Re: Question: Formula Force GP (Force) Feedback

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi,

my description wasn't that good indeed :)

I have an Logitech Formula Force GP and tried to get Force Feedback
working (to play LFS (www.lfs.net) via wine). The wheel is recognized
properly and as an input device there are no problems. Force Feedback
isn't working properly, tho. I looked around a bit and found hid-lgff.c,
which i have modified a bit to get an idea how the effects are
generated:

    case FF_CONSTANT:
        x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */
        y = effect->u.ramp.end_level   + 0x7f; /**/
        CLAMP(x);
        CLAMP(y);
        report->field[0]->value[0] = 0x51;
        report->field[0]->value[1] = 0x08;
        report->field[0]->value[2] = x;
        report->field[0]->value[3] = y;
        dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
        usbhid_submit_report(hid, report, USB_DIR_OUT);
>       printk(KERN_INFO "Forces: x=%d y=%d\n",
>		report->field[0]->value[2], 
>		report->field[0]->value[3]);
        break;

I guess i could have enabled debug somehow, but didn't know how to. It
did the trick for me anyway. 

The problem is, that somehow x and y only get values above 0x7f. So
(with a hacked wine, http://bugs.winehq.org/show_bug.cgi?id=9221) it
gives correct force strength, but only to the left. With ffcfstress
(modified for center position not moving) i get the same result, but its
a bit tricky to describe... right from center you have to push harder
the more you turn right (correct) and left from center you have to hold
to the right the more you turn left. In principle the same happens in
LFS.

it should be: <<<  <<  <  o  >  >>  >>>

it is:        >>>  >>  >  o  >  >>  >>>

effect->u.ramp.start_level and 
effect->u.ramp.end_level 
both seem to get ripped off their sign (+/-) somehow.

I'm running gentoo and thus a customized kernel (2.6.32-gentoo-r1). I'll
try a vanilla kernel soon, just to be sure.
If you need further info about my system or configuration, just ask.


Thx, Peter

attached:
modified hid-lgff.c from kernel,
modified effect_linuxinput.c from wine-1.1.35
modified ffcfstress.c from ff-utils

On Wed, 2010-01-06 at 11:43 +0100, Jiri Kosina wrote:
> [ adding some more CCs ]
> 
> I haven't been really succesfull extracting the nature of the problem from 
> the mail below. Could you please sum it up for the people who haven't seen 
> the thread from the beginning?
> 
> Thanks.
> 

/*
 * Force feedback support for hid-compliant for some of the devices from
 * Logitech, namely:
 * - WingMan Cordless RumblePad
 * - WingMan Force 3D
 *
 *  Copyright (c) 2002-2004 Johann Deneux
 *  Copyright (c) 2006 Anssi Hannula <anssi.hannula@xxxxxxxxx>
 */

/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
 *
 * Should you need to contact me, the author, you can do so by
 * e-mail - mail your message to <johann.deneux@xxxxxxxx>
 */

#include <linux/input.h>
#include <linux/usb.h>
#include <linux/hid.h>

#include "usbhid/usbhid.h"
#include "hid-lg.h"

struct dev_type {
	u16 idVendor;
	u16 idProduct;
	const signed short *ff;
};

static const signed short ff_rumble[] = {
	FF_RUMBLE,
	-1
};

static const signed short ff_joystick[] = {
	FF_CONSTANT,
	-1
};

static const signed short ff_joystick_ac[] = {
	FF_CONSTANT,
	FF_AUTOCENTER,
	-1
};

static const signed short ff_wheel[] = {
	FF_CONSTANT,
	FF_RAMP,
	FF_AUTOCENTER,
	-1
};

static const struct dev_type devices[] = {
	{ 0x046d, 0xc211, ff_rumble },
	{ 0x046d, 0xc219, ff_rumble },
	{ 0x046d, 0xc283, ff_joystick },
	{ 0x046d, 0xc286, ff_joystick_ac },
	{ 0x046d, 0xc293, ff_wheel },
	{ 0x046d, 0xc294, ff_wheel },
	{ 0x046d, 0xc295, ff_joystick },
	{ 0x046d, 0xca03, ff_wheel },
};

static int hid_lgff_play(struct input_dev *dev, void *data, struct ff_effect *effect)
{
	struct hid_device *hid 		= input_get_drvdata(dev);
	struct list_head  *report_list 	= &hid->report_enum[HID_OUTPUT_REPORT].report_list;
	struct hid_report *report 	= list_entry(report_list->next, struct hid_report, list);
	signed int x, y;
	unsigned int left, right;

#define CLAMP(x) if (x < 0) x = 0; if (x > 0xff) x = 0xff

	switch (effect->type) 
	{
		case FF_CONSTANT:
			x = effect->u.ramp.start_level + 0x7f; /* 0x7f is center */
			y = effect->u.ramp.end_level   + 0x7f; /**/
			CLAMP(x);
			CLAMP(y); 
			report->field[0]->value[0] = 0x51;
			report->field[0]->value[1] = 0x08;
			report->field[0]->value[2] = x;
			report->field[0]->value[3] = y;
			dbg_hid("(x, y)=(%04x, %04x)\n", x, y);
			usbhid_submit_report(hid, report, USB_DIR_OUT);
			printk(KERN_INFO "Forces: x=%d y=%d\n", report->field[0]->value[2], report->field[0]->value[3]);
			break;

		case FF_RUMBLE:
			right = effect->u.rumble.strong_magnitude;
			left = effect->u.rumble.weak_magnitude;
			right = right * 0xff / 0xffff;
			left = left * 0xff / 0xffff;
			CLAMP(left);
			CLAMP(right);
			report->field[0]->value[0] = 0x42;
			report->field[0]->value[1] = 0x00;
			report->field[0]->value[2] = left;
			report->field[0]->value[3] = right;
			dbg_hid("(left, right)=(%04x, %04x)\n", left, right);
			usbhid_submit_report(hid, report, USB_DIR_OUT);
			break;
	}
	return 0;
}

static void hid_lgff_set_autocenter(struct input_dev *dev, u16 magnitude)
{
	struct hid_device *hid = input_get_drvdata(dev);
	struct list_head *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
	struct hid_report *report = list_entry(report_list->next, struct hid_report, list);
	__s32 *value 	= report->field[0]->value;
/*	magnitude 	= (magnitude >> 12) & 0xf;*/
	*value++ 	= 0xfe;
	*value++ 	= 0x0d;
	*value++ 	= magnitude;   /* clockwise strength */
	*value++ 	= magnitude;   /* counter-clockwise strength */
	*value++ 	= 0x80;
	*value++ 	= 0x00;
	*value 		= 0x00;
	usbhid_submit_report(hid, report, USB_DIR_OUT);
}

int lgff_init(struct hid_device* hid)
{
	struct hid_input   *hidinput 	= list_entry(hid->inputs.next, struct hid_input, list);
	struct list_head   *report_list = &hid->report_enum[HID_OUTPUT_REPORT].report_list;
	struct input_dev   *dev 	= hidinput->input;
	struct hid_report  *report;
	struct hid_field   *field;
	const signed short *ff_bits 	= ff_joystick;
	int error;
	int i;

	/* Find the report to use */
	if (list_empty(report_list)) 
	{
		err_hid("No output report found");
		return -1;
	}

	/* Check that the report looks ok */
	report = list_entry(report_list->next, struct hid_report, list);
	field  = report->field[0];
	if (!field) 
	{
		err_hid("NULL field");
		return -1;
	}

	for (i = 0; i < ARRAY_SIZE(devices); i++) 
	{
		if (dev->id.vendor  == devices[i].idVendor &&
		    dev->id.product == devices[i].idProduct) 
		{
			ff_bits = devices[i].ff;
			break;
		}
	}

	for (i = 0; ff_bits[i] >= 0; i++) 
	{
		set_bit(ff_bits[i], dev->ffbit);
	}

	error = input_ff_create_memless(dev, NULL, hid_lgff_play);

	if (error)
	{
		return error;
	}

	if ( test_bit(FF_AUTOCENTER, dev->ffbit) )
	{
		dev->ff->set_autocenter = hid_lgff_set_autocenter;
	}

	printk(KERN_INFO "Force feedback for Logitech force feedback devices by Johann Deneux <johann.deneux@xxxxxxxx>\n");

	return 0;
}
/*			  DirectInput Linux Event Device Effect
 *
 * Copyright 2005 Daniel Remenak
 *
 * Thanks to Google's Summer of Code Program (2005)
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
 */

#include "config.h"

#ifdef HAVE_STRUCT_FF_EFFECT_DIRECTION

#include <stdarg.h>
#include <string.h>
#ifdef HAVE_LINUX_INPUT_H
#  include <linux/input.h>
#  undef SW_MAX
#endif
#include <errno.h>
#ifdef HAVE_UNISTD_H
#  include <unistd.h>
#endif
#include <math.h>
#include "wine/debug.h"
#include "wine/unicode.h"
#include "windef.h"
#include "winbase.h"
#include "winerror.h"
#include "dinput.h"

#include "device_private.h"

WINE_DEFAULT_DEBUG_CHANNEL(dinput);

static const IDirectInputEffectVtbl LinuxInputEffectVtbl;
typedef struct LinuxInputEffectImpl LinuxInputEffectImpl;

struct LinuxInputEffectImpl
{
	const void 		*lpVtbl;
	LONG			ref;
	GUID			guid;

	struct ff_effect	effect; /* Effect data */
	int			gain;   /* Effect gain */
	int			first_axis_is_x;
	int*			fd;	 /* Parent device */
	struct list		*entry;  /* Entry into the parent's list of effects */
};


/******************************************************************************
 *	  DirectInputEffect Functional Helper
 */

static DWORD _typeFromGUID(REFGUID guid)
{
	if (IsEqualGUID(guid, &GUID_ConstantForce)) {
		return DIEFT_CONSTANTFORCE;
	} else if (IsEqualGUID(guid, &GUID_Square)
		|| IsEqualGUID(guid, &GUID_Sine)
		|| IsEqualGUID(guid, &GUID_Triangle)
		|| IsEqualGUID(guid, &GUID_SawtoothUp)
		|| IsEqualGUID(guid, &GUID_SawtoothDown)) 
	{
		return DIEFT_PERIODIC;
	} else if (IsEqualGUID(guid, &GUID_RampForce)) 
	{
		return DIEFT_RAMPFORCE;
	} else if (IsEqualGUID(guid, &GUID_Spring)
		|| IsEqualGUID(guid, &GUID_Damper)
		|| IsEqualGUID(guid, &GUID_Inertia)
		|| IsEqualGUID(guid, &GUID_Friction)) 
	{
		return DIEFT_CONDITION;
	} else if (IsEqualGUID(guid, &GUID_CustomForce)) 
	{
		return DIEFT_CUSTOMFORCE;
	} else {
		WARN("GUID (%s) is not a known force type\n", _dump_dinput_GUID(guid));
		return 0;
	}
}


/******************************************************************************
 *	  DirectInputEffect debug helpers 
 */

static void _dump_DIEFFECT_flags(DWORD dwFlags)
{
	if (TRACE_ON(dinput)) {
		unsigned int   i;
		static const struct {
			DWORD	   mask;
			const char  *name;
		} flags[] = {
#define FE(x) { x, #x}
			FE(DIEFF_CARTESIAN),
			FE(DIEFF_OBJECTIDS),
			FE(DIEFF_OBJECTOFFSETS),
			FE(DIEFF_POLAR),
			FE(DIEFF_SPHERICAL)
#undef FE
		};

		for (i = 0; i < (sizeof(flags) / sizeof(flags[0])); i++)
		{
			if (flags[i].mask & dwFlags) 
			{
				TRACE("%s ", flags[i].name);
			}
		}
		TRACE("\n");
	}	   
}



static void _dump_DIENVELOPE(LPCDIENVELOPE env)
{
	if (env->dwSize != sizeof(DIENVELOPE)) 
	{
		WARN("Non-standard DIENVELOPE structure size %d.\n", env->dwSize);
	}

	TRACE("Envelope has attack (level: %d time: %d), fade (level: %d time: %d)\n", env->dwAttackLevel, env->dwAttackTime, env->dwFadeLevel, env->dwFadeTime);
} 



static void _dump_DICONSTANTFORCE(LPCDICONSTANTFORCE frc)
{
	TRACE("Constant force has magnitude %d\n", frc->lMagnitude);
}



static void _dump_DIPERIODIC(LPCDIPERIODIC frc)
{
	TRACE("Periodic force has magnitude %d, offset %d, phase %d, period %d\n", frc->dwMagnitude, frc->lOffset, frc->dwPhase, frc->dwPeriod);
}



static void _dump_DIRAMPFORCE(LPCDIRAMPFORCE frc)
{
	TRACE("Ramp force has start %d, end %d\n", frc->lStart, frc->lEnd);
}



static void _dump_DICONDITION(LPCDICONDITION frc)
{
	TRACE("Condition has offset %d, pos/neg coefficients %d and %d, pos/neg saturations %d and %d, deadband %d\n",
		frc->lOffset, frc->lPositiveCoefficient, frc->lNegativeCoefficient, frc->dwPositiveSaturation, frc->dwNegativeSaturation, frc->lDeadBand);
}



static void _dump_DICUSTOMFORCE(LPCDICUSTOMFORCE frc)
{
	unsigned int i;
	TRACE("Custom force uses %d channels, sample period %d.  Has %d samples at %p.\n", frc->cChannels, frc->dwSamplePeriod, frc->cSamples, frc->rglForceData);
	if (frc->cSamples % frc->cChannels != 0)
		WARN("Custom force has a non-integral samples-per-channel count!\n");
	if (TRACE_ON(dinput)) {
		TRACE("Custom force data (time aligned, axes in order):\n");
		for (i = 1; i <= frc->cSamples; ++i) {
			TRACE("%d ", frc->rglForceData[i]);
			if (i % frc->cChannels == 0)
				TRACE("\n");
		}	
	}
}



static void _dump_DIEFFECT(LPCDIEFFECT eff, REFGUID guid)
{
	unsigned int i;
	DWORD type = _typeFromGUID(guid);

	TRACE("Dumping DIEFFECT structure:\n");
	TRACE("  - dwSize: %d\n", eff->dwSize);
	if ((eff->dwSize != sizeof(DIEFFECT)) && (eff->dwSize != sizeof(DIEFFECT_DX5))) 
	{
		WARN("Non-standard DIEFFECT structure size %d\n", eff->dwSize);
	}
	TRACE("  - dwFlags: %d\n", eff->dwFlags);
	TRACE("	");
	_dump_DIEFFECT_flags(eff->dwFlags); 
	TRACE("  - dwDuration: %d\n", eff->dwDuration);
	TRACE("  - dwGain: %d\n", eff->dwGain);
	if (eff->dwGain > 10000)
		WARN("dwGain is out of range (>10,000)\n");
	TRACE("  - dwTriggerButton: %d\n", eff->dwTriggerButton);
	TRACE("  - dwTriggerRepeatInterval: %d\n", eff->dwTriggerRepeatInterval);
	TRACE("  - cAxes: %d\n", eff->cAxes);
	TRACE("  - rgdwAxes: %p\n", eff->rgdwAxes);
	if (TRACE_ON(dinput) && eff->rgdwAxes) {
		TRACE("	");	
		for (i = 0; i < eff->cAxes; ++i)
			TRACE("%d ", eff->rgdwAxes[i]);
		TRACE("\n");
	}
	TRACE("  - rglDirection: %p\n", eff->rglDirection);
	TRACE("  - lpEnvelope: %p\n", eff->lpEnvelope);
	TRACE("  - cbTypeSpecificParams: %d\n", eff->cbTypeSpecificParams);
	TRACE("  - lpvTypeSpecificParams: %p\n", eff->lpvTypeSpecificParams);
	if (eff->dwSize > sizeof(DIEFFECT_DX5))
		TRACE("  - dwStartDelay: %d\n", eff->dwStartDelay);
	if (eff->lpEnvelope != NULL)
		_dump_DIENVELOPE(eff->lpEnvelope);
	if (type == DIEFT_CONSTANTFORCE) {
		if (eff->cbTypeSpecificParams != sizeof(DICONSTANTFORCE)) {
			WARN("Effect claims to be a constant force but the type-specific params are the wrong size!\n"); 
		} else {
			_dump_DICONSTANTFORCE(eff->lpvTypeSpecificParams);
		}
	} else if (type == DIEFT_PERIODIC) { 
		if (eff->cbTypeSpecificParams != sizeof(DIPERIODIC)) {
			WARN("Effect claims to be a periodic force but the type-specific params are the wrong size!\n");
		} else {
			_dump_DIPERIODIC(eff->lpvTypeSpecificParams);
		}
	} else if (type == DIEFT_RAMPFORCE) {
		if (eff->cbTypeSpecificParams != sizeof(DIRAMPFORCE)) {
			WARN("Effect claims to be a ramp force but the type-specific params are the wrong size!\n");
		} else {
			_dump_DIRAMPFORCE(eff->lpvTypeSpecificParams);
		}
	} else if (type == DIEFT_CONDITION) { 
		if (eff->cbTypeSpecificParams != sizeof(DICONDITION)) {
			WARN("Effect claims to be a condition but the type-specific params are the wrong size!\n");
		} else {
			_dump_DICONDITION(eff->lpvTypeSpecificParams);
		}
	} else if (type == DIEFT_CUSTOMFORCE) {
		if (eff->cbTypeSpecificParams != sizeof(DICUSTOMFORCE)) {
			WARN("Effect claims to be a custom force but the type-specific params are the wrong size!\n");
		} else {
			_dump_DICUSTOMFORCE(eff->lpvTypeSpecificParams);
		}
	}
}



/******************************************************************************
 *	  LinuxInputEffectImpl 
 */

static ULONG WINAPI LinuxInputEffectImpl_AddRef(LPDIRECTINPUTEFFECT iface)
{
	LinuxInputEffectImpl *This = (LinuxInputEffectImpl *)iface;
	return InterlockedIncrement(&(This->ref));
}



static HRESULT WINAPI LinuxInputEffectImpl_Download(LPDIRECTINPUTEFFECT iface)
{
	LinuxInputEffectImpl *This = (LinuxInputEffectImpl *)iface;

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

	if (ioctl(*(This->fd), EVIOCSFF, &This->effect) == -1) {
		if (errno == ENOMEM) {
			return DIERR_DEVICEFULL;
		} else {
			FIXME("Could not upload effect. Assuming a disconnected device %d \"%s\".\n", *This->fd, strerror(errno));
			return DIERR_INPUTLOST;
		}
	}

	return DI_OK;
}



static HRESULT WINAPI LinuxInputEffectImpl_Escape(LPDIRECTINPUTEFFECT iface, LPDIEFFESCAPE pesc)
{
	WARN("(this=%p,%p): invalid: no hardware-specific escape codes in this driver!\n", iface, pesc);

	return DI_OK;
}



static HRESULT WINAPI LinuxInputEffectImpl_GetEffectGuid(LPDIRECTINPUTEFFECT iface, LPGUID pguid)
{
	LinuxInputEffectImpl *This = (LinuxInputEffectImpl*)iface;

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

	pguid = &This->guid;
	
	return DI_OK;
}



static HRESULT WINAPI LinuxInputEffectImpl_GetEffectStatus(LPDIRECTINPUTEFFECT iface, LPDWORD pdwFlags)
{
	TRACE("(this=%p,%p)\n", iface, pdwFlags);

	/* linux sends the effect status through an event.
	 * that event is trapped by our parent joystick driver
	 * and there is no clean way to pass it back to us. */
	FIXME("Not enough information to provide a status.\n");

	(*pdwFlags) = 0;

	return DI_OK;
}

static HRESULT WINAPI LinuxInputEffectImpl_GetParameters(LPDIRECTINPUTEFFECT iface, LPDIEFFECT peff, DWORD dwFlags)
{
	HRESULT diErr 			= DI_OK;
	LinuxInputEffectImpl *This 	= (LinuxInputEffectImpl *)iface;
	TRACE("(this=%p,%p,%d)\n", This, peff, dwFlags);

	/* Major conversion factors are:
	 * times: millisecond (linux) -> microsecond (windows) (x * 1000)
	 * forces: scale 0x7FFF (linux) -> scale 10000 (windows) approx ((x / 33) * 10)
	 * angles: scale 0x7FFF (linux) -> scale 35999 (windows) approx ((x / 33) * 36)
	 * angle bases: 0 -> -y (down) (linux) -> 0 -> +x (right) (windows)
	 */

	if (dwFlags & DIEP_AXES) {
		if (peff->cAxes < 2 /* linuxinput effects always use 2 axes, x and y */)
			diErr = DIERR_MOREDATA;
		peff->cAxes = 2; 
		if (diErr)
			return diErr;
		else {
			peff->rgdwAxes[0] = DIJOFS_X;
			peff->rgdwAxes[1] = DIJOFS_Y;
		}
	}
 
	if (dwFlags & DIEP_DIRECTION) {
		if (peff->cAxes < 2)
			diErr = DIERR_MOREDATA;
		peff->cAxes = 2; 
		if (diErr)
			return diErr;
		else {
			if (peff->dwFlags & DIEFF_CARTESIAN) {
			peff->rglDirection[0] = (long)(sin(M_PI * 3 * This->effect.direction / 0x7FFF) * 1000);
			peff->rglDirection[1] = (long)(cos(M_PI * 3 * This->effect.direction / 0x7FFF) * 1000);
		} else {
			/* Polar and spherical coordinates are the same for two or less
			 * axes.
			 * Note that we also use this case if NO flags are marked.
			 * According to MSDN, we should return the direction in the
			 * format that it was specified in, if no flags are marked.
			 */
			peff->rglDirection[0] = (This->effect.direction / 33) * 36 + 9000;
			if (peff->rglDirection[0] > 35999)
				peff->rglDirection[0] -= 35999;
			}
		}
	}

	if (dwFlags & DIEP_DURATION) {
		peff->dwDuration = (DWORD)This->effect.replay.length * 1000;
	}

	if (dwFlags & DIEP_ENVELOPE) {
		struct ff_envelope* env;
		if (This->effect.type == FF_CONSTANT)		env = &This->effect.u.constant.envelope;
		else if (This->effect.type == FF_PERIODIC) 	env = &This->effect.u.periodic.envelope;
		else if (This->effect.type == FF_RAMP) 		env = &This->effect.u.ramp.envelope;
		else 						env = NULL;

		if (env == NULL) {
			peff->lpEnvelope = NULL;
		} else if (peff->lpEnvelope == NULL) {
			return DIERR_INVALIDPARAM;
		} else { 
			peff->lpEnvelope->dwAttackLevel = (env->attack_level / 33) * 10;
			peff->lpEnvelope->dwAttackTime 	=  env->attack_length * 1000;
			peff->lpEnvelope->dwFadeLevel 	= (env->fade_level / 33) * 10;
			peff->lpEnvelope->dwFadeTime 	=  env->fade_length * 1000;
		}
	}

	if (dwFlags & DIEP_GAIN) {
		peff->dwGain = This->gain * 10000 / 0xFFFF;
	}

	if (dwFlags & DIEP_SAMPLEPERIOD) {
		/* the linux input ff driver has no support for setting
		 * the playback sample period.  0 means default. */
		peff->dwSamplePeriod = 0;
	}

	if (dwFlags & DIEP_STARTDELAY) {
		peff->dwStartDelay = This->effect.replay.delay * 1000;
	}

	if (dwFlags & DIEP_TRIGGERBUTTON) {
		FIXME("LinuxInput button mapping needs redoing; for now, assuming we're using an actual joystick.\n");
		peff->dwTriggerButton = DIJOFS_BUTTON(This->effect.trigger.button - BTN_JOYSTICK);
	}

	if (dwFlags & DIEP_TRIGGERREPEATINTERVAL) {
		peff->dwTriggerRepeatInterval = This->effect.trigger.interval * 1000;
	}

	if (dwFlags & DIEP_TYPESPECIFICPARAMS) {
		DWORD expectedsize = 0;
		if 	  (This->effect.type == FF_PERIODIC) {
			expectedsize = sizeof(DIPERIODIC);
		} else if (This->effect.type == FF_CONSTANT) {
			expectedsize = sizeof(DICONSTANTFORCE);
		} else if (This->effect.type == FF_SPRING || This->effect.type == FF_FRICTION || This->effect.type == FF_INERTIA || This->effect.type == FF_DAMPER) {
			expectedsize = sizeof(DICONDITION) * 2;
		} else if (This->effect.type == FF_RAMP) {
			expectedsize = sizeof(DIRAMPFORCE);
		}

		if (expectedsize > peff->cbTypeSpecificParams)
			diErr = DIERR_MOREDATA;

		peff->cbTypeSpecificParams = expectedsize;

		if (diErr)
			return diErr;
		else {
			if (This->effect.type == FF_PERIODIC) {
				LPDIPERIODIC tsp 	= peff->lpvTypeSpecificParams;
				tsp->dwMagnitude 	= (This->effect.u.periodic.magnitude / 33) * 10;
				tsp->lOffset 		= (This->effect.u.periodic.offset / 33) * 10;
				tsp->dwPhase 		= (This->effect.u.periodic.phase / 33) * 36;
				tsp->dwPeriod 		= (This->effect.u.periodic.period * 1000);
			} else if (This->effect.type == FF_CONSTANT) {
				LPDICONSTANTFORCE tsp 	= peff->lpvTypeSpecificParams;
				tsp->lMagnitude 	= (This->effect.u.constant.level / 33) * 10;
			} else if (This->effect.type == FF_SPRING || This->effect.type == FF_FRICTION || This->effect.type == FF_INERTIA || This->effect.type == FF_DAMPER) {
				LPDICONDITION tsp 	= peff->lpvTypeSpecificParams;
			int i;
			for (i = 0; i < 2; ++i) {
				tsp[i].lOffset 			= (This->effect.u.condition[i].center / 33) * 10; 
				tsp[i].lPositiveCoefficient 	= (This->effect.u.condition[i].right_coeff / 33) * 10;
				tsp[i].lNegativeCoefficient 	= (This->effect.u.condition[i].left_coeff / 33) * 10; 
				tsp[i].dwPositiveSaturation 	= (This->effect.u.condition[i].right_saturation / 33) * 10;
				tsp[i].dwNegativeSaturation 	= (This->effect.u.condition[i].left_saturation / 33) * 10;
				tsp[i].lDeadBand 		= (This->effect.u.condition[i].deadband / 33) * 10;
			}
			} else if (This->effect.type == FF_RAMP) {
				LPDIRAMPFORCE tsp 	= peff->lpvTypeSpecificParams;
				tsp->lStart 		= (This->effect.u.ramp.start_level / 33) * 10;
				tsp->lEnd 		= (This->effect.u.ramp.end_level / 33) * 10;
			}
		}
	} 

	return diErr;
}



static HRESULT WINAPI LinuxInputEffectImpl_Initialize(LPDIRECTINPUTEFFECT iface, HINSTANCE hinst, DWORD dwVersion, REFGUID rguid)
{
	FIXME("(this=%p,%p,%d,%s): stub!\n", iface, hinst, dwVersion, debugstr_guid(rguid));

	return DI_OK;
}



static HRESULT WINAPI LinuxInputEffectImpl_QueryInterface(LPDIRECTINPUTEFFECT iface, REFIID riid, void **ppvObject)
{
	LinuxInputEffectImpl* This = (LinuxInputEffectImpl*)iface;

	TRACE("(this=%p,%s,%p)\n", This, debugstr_guid(riid), ppvObject);

	if (IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IDirectInputEffect, riid)) {
		LinuxInputEffectImpl_AddRef(iface);
		*ppvObject = This;
		return 0;
	}

	TRACE("Unsupported interface!\n");
	return E_FAIL;
}



static HRESULT WINAPI LinuxInputEffectImpl_Start(LPDIRECTINPUTEFFECT iface, DWORD dwIterations, DWORD dwFlags)
{
	struct input_event event;
	LinuxInputEffectImpl* This = (LinuxInputEffectImpl*)iface;

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

	if (!(dwFlags & DIES_NODOWNLOAD)) {
		/* Download the effect if necessary */
		if (This->effect.id == -1) {
			HRESULT res = LinuxInputEffectImpl_Download(iface);
			if (res != DI_OK)
				return res;
		}
	}

	if (dwFlags & DIES_SOLO) {
		FIXME("Solo mode requested: should be stopping all effects here!\n");
	}

	event.type 	= EV_FF;
	event.code 	= This->effect.id;
	event.value 	= dwIterations;

	if (write(*(This->fd), &event, sizeof(event)) == -1) {
		FIXME("Unable to write event.  Assuming device disconnected.\n");
		return DIERR_INPUTLOST;
	}

	return DI_OK;
}

static HRESULT WINAPI LinuxInputEffectImpl_SetParameters(LPDIRECTINPUTEFFECT iface, LPCDIEFFECT peff, DWORD dwFlags) 
{	   
	LinuxInputEffectImpl* This 	= (LinuxInputEffectImpl*)iface; 
	DWORD type 			= _typeFromGUID(&This->guid);
	HRESULT retval 			= DI_OK;

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

	_dump_DIEFFECT(peff, &This->guid);

	if ((dwFlags & ~DIEP_NORESTART & ~DIEP_NODOWNLOAD & ~DIEP_START) == 0) {
		/* set everything */
		dwFlags = DIEP_AXES | DIEP_DIRECTION | DIEP_DURATION | DIEP_ENVELOPE | DIEP_GAIN | DIEP_SAMPLEPERIOD | DIEP_STARTDELAY | DIEP_TRIGGERBUTTON | DIEP_TRIGGERREPEATINTERVAL | DIEP_TYPESPECIFICPARAMS;
	}

	if (dwFlags & DIEP_AXES) {
		/* the linux input effect system only supports one or two axes */
		if (peff->cAxes > 2)
			return DIERR_INVALIDPARAM;
#if (0)
		else if (peff->cAxes < 1)
			return DIERR_INCOMPLETEEFFECT;
#endif
		This->first_axis_is_x = peff->rgdwAxes[0] == DIJOFS_X;
	}

	/* some of this may look funky, but it's 'cause the linux driver and directx have
	 * different opinions about which way direction "0" is.  directx has 0 along the x
	 * axis (left), linux has it along the y axis (down). */ 
	if (dwFlags & DIEP_DIRECTION) {
		if (peff->cAxes == 1) {
			if (peff->dwFlags & DIEFF_CARTESIAN) {
				if (dwFlags & DIEP_AXES) {
					if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] >= 0)
						This->effect.direction = 0x4000;
					else if (peff->rgdwAxes[0] == DIJOFS_X && peff->rglDirection[0] < 0)
						This->effect.direction = 0xC000;
					else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] >= 0)
						This->effect.direction = 0x0000;
					else if (peff->rgdwAxes[0] == DIJOFS_Y && peff->rglDirection[0] < 0)
						This->effect.direction = 0x8000;
				}
			} else {
				/* one-axis effects must use cartesian coords */
				return DIERR_INVALIDPARAM;
			}
#if (0) /* gerald: replacing 'else' with 'else if' since cAxes can now be 0 : */
	 	} else { /* two axes */
#endif
		} else if (peff->cAxes == 2) { /* two axes */
			if (peff->dwFlags & DIEFF_CARTESIAN) {
				LONG x, y;
				if (This->first_axis_is_x) {
					x = peff->rglDirection[1];
					y = peff->rglDirection[1];
				} else {
					x = peff->rglDirection[0];
					y = peff->rglDirection[0];
				}
				This->effect.direction = (int)((3 * M_PI / 2 - atan2(y, x)) * -0x7FFF / M_PI);
			} else {
				/* Polar and spherical are the same for 2 axes */
				/* Precision is important here, so we do double math with exact constants */
				This->effect.direction = (int)(((double)peff->rglDirection[0] - 90) / 35999) * 0x7FFF;
			}
		}
	}

	if (dwFlags & DIEP_DURATION)
		This->effect.replay.length = peff->dwDuration / 1000;

	if (dwFlags & DIEP_ENVELOPE) {
		struct ff_envelope* env;
		if (This->effect.type == FF_CONSTANT) 		env = &This->effect.u.constant.envelope;
		else if (This->effect.type == FF_PERIODIC) 	env = &This->effect.u.periodic.envelope;
		else if (This->effect.type == FF_RAMP) 		env = &This->effect.u.ramp.envelope;
		else 						env = NULL; 

	 	if (peff->lpEnvelope == NULL) {
	 		/* if this type had an envelope, reset it
	 		 * note that length can never be zero, so we set it to something minuscule */
#if (0) 		/* gerald: That thing did make my FF wheel immitate a pneumatic hammer,
			 * I mean, violent repetitive jerking whenever FF is "playing".
			 * Simply commenting out works at least in my case with GPLegends & LFS.
			 * ...
			 * Maybe could try to set attack (and fade ?) to the same as what
			 * effect.u.constant.level is set to (when doing CONSTANTFORCE) ? */
	 		if (env) {
	 			env->attack_length	= 0x10;
	 			env->attack_level 	= 0x7FFF;
	 			env->fade_length 	= 0x10;
	 			env->fade_level 	= 0x7FFF;
	 		}
#endif
		} else {
			/* did we get passed an envelope for a type that doesn't even have one? */
			if (!env) 
				return DIERR_INVALIDPARAM;
			/* copy the envelope */
			env->attack_length	=  peff->lpEnvelope->dwAttackTime / 1000;
			env->attack_level 	= (peff->lpEnvelope->dwAttackLevel / 10) * 32;
			env->fade_length 	=  peff->lpEnvelope->dwFadeTime / 1000;
			env->fade_level 	= (peff->lpEnvelope->dwFadeLevel / 10) * 32;
		}
	}

	/* Gain and Sample Period settings are not supported by the linux
	 * event system */
	if (dwFlags & DIEP_GAIN) {
		This->gain = 0xFFFF * peff->dwGain / 10000;
		TRACE("Effect gain requested but no effect gain functionality present.\n");
	}

	if (dwFlags & DIEP_SAMPLEPERIOD)
		TRACE("Sample period requested but no sample period functionality present.\n");

	if (dwFlags & DIEP_STARTDELAY)
		This->effect.replay.delay = peff->dwStartDelay / 1000;

	This->effect.replay.delay = 0;

	if (dwFlags & DIEP_TRIGGERBUTTON) {
		if (peff->dwTriggerButton != -1) {
			FIXME("Linuxinput button mapping needs redoing, assuming we're using a joystick.\n");
			FIXME("Trigger button translation not yet implemented!\n");
		}
		This->effect.trigger.button = 0;
	}

	if (dwFlags & DIEP_TRIGGERREPEATINTERVAL)
		This->effect.trigger.interval = peff->dwTriggerRepeatInterval / 1000;
	
	if (dwFlags & DIEP_TYPESPECIFICPARAMS) {
		if (!(peff->lpvTypeSpecificParams))
			return DIERR_INCOMPLETEEFFECT;
		if (type == DIEFT_PERIODIC) {
			LPCDIPERIODIC tsp;
			if (peff->cbTypeSpecificParams != sizeof(DIPERIODIC)) 
			{
				return DIERR_INVALIDPARAM;
			}
			tsp = peff->lpvTypeSpecificParams;
			This->effect.u.periodic.magnitude 	= (tsp->dwMagnitude / 10) * 32;
			This->effect.u.periodic.offset 		= (tsp->lOffset     / 10) * 32;
			This->effect.u.periodic.phase 		= (tsp->dwPhase     / 9 ) * 8; /* == (/ 36 * 32) */
			This->effect.u.periodic.period 		= (tsp->dwPeriod    / 1000);
		} else if (type == DIEFT_CONSTANTFORCE) {
			LPCDICONSTANTFORCE tsp;
#if (0)
			if (peff->cbTypeSpecificParams != sizeof(DICONSTANTFORCE)) 
			{
				return DIERR_INVALIDPARAM;
			} 
#endif
			tsp = peff->lpvTypeSpecificParams;
			This->effect.u.constant.level = (max(min(tsp->lMagnitude, 10000), -10000) / 10) * 32;
		} else if (type == DIEFT_RAMPFORCE) {
			LPCDIRAMPFORCE tsp;
			if (peff->cbTypeSpecificParams != sizeof(DIRAMPFORCE))
			{
				return DIERR_INVALIDPARAM;
			}
			tsp = peff->lpvTypeSpecificParams;
			This->effect.u.ramp.start_level = (tsp->lStart / 10) * 32;
			This->effect.u.ramp.end_level = (tsp->lStart / 10) * 32;
		} else if (type == DIEFT_CONDITION) {
			LPCDICONDITION tsp = peff->lpvTypeSpecificParams;
			if (peff->cbTypeSpecificParams == sizeof(DICONDITION)) {
				/* One condition block.  This needs to be rotated to direction,
				 * and expanded to separate x and y conditions. */
				int i;
				double factor[2];
				factor[0] = asin((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
				factor[1] = acos((This->effect.direction * 3.0 * M_PI) / 0x7FFF);
				for (i = 0; i < 2; ++i) {
					This->effect.u.condition[i].center = (int)(factor[i] * (tsp->lOffset / 10) * 32);
					This->effect.u.condition[i].right_coeff = (int)(factor[i] * (tsp->lPositiveCoefficient / 10) * 32);
					This->effect.u.condition[i].left_coeff = (int)(factor[i] * (tsp->lNegativeCoefficient / 10) * 32); 
					This->effect.u.condition[i].right_saturation = (int)(factor[i] * (tsp->dwPositiveSaturation / 10) * 32);
					This->effect.u.condition[i].left_saturation = (int)(factor[i] * (tsp->dwNegativeSaturation / 10) * 32);
					This->effect.u.condition[i].deadband = (int)(factor[i] * (tsp->lDeadBand / 10) * 32);
				}
			} else if (peff->cbTypeSpecificParams == 2 * sizeof(DICONDITION)) {
				/* Two condition blocks.  Direct parameter copy. */
				int i;
				for (i = 0; i < 2; ++i) {
					This->effect.u.condition[i].center = (tsp[i].lOffset / 10) * 32;
					This->effect.u.condition[i].right_coeff = (tsp[i].lPositiveCoefficient / 10) * 32;
					This->effect.u.condition[i].left_coeff = (tsp[i].lNegativeCoefficient / 10) * 32;
					This->effect.u.condition[i].right_saturation = (tsp[i].dwPositiveSaturation / 10) * 32;
					This->effect.u.condition[i].left_saturation = (tsp[i].dwNegativeSaturation / 10) * 32;
					This->effect.u.condition[i].deadband = (tsp[i].lDeadBand / 10) * 32;
				}
			} else {
				return DIERR_INVALIDPARAM;
			}
		} else {
			FIXME("Custom force types are not supported\n");	
			return DIERR_INVALIDPARAM;
		}
	}

	if (!(dwFlags & DIEP_NODOWNLOAD))
		retval = LinuxInputEffectImpl_Download(iface);
	if (retval != DI_OK)
		return DI_DOWNLOADSKIPPED;

	if (dwFlags & DIEP_NORESTART)
		TRACE("DIEP_NORESTART: not handled (we have no control of that).\n");

	if (dwFlags & DIEP_START)
		retval = LinuxInputEffectImpl_Start(iface, 1, 0);
	if (retval != DI_OK)
		return retval;
 
	return DI_OK;
}   



static HRESULT WINAPI LinuxInputEffectImpl_Stop(LPDIRECTINPUTEFFECT iface)
{
	struct input_event event;
	LinuxInputEffectImpl *This = (LinuxInputEffectImpl *)iface;

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

	event.type 	= EV_FF;
	event.code 	= This->effect.id;
	event.value 	= 0;
	/* we don't care about the success or failure of this call */
	write(*(This->fd), &event, sizeof(event));

	return DI_OK;
}



static HRESULT WINAPI LinuxInputEffectImpl_Unload(LPDIRECTINPUTEFFECT iface)
{
	LinuxInputEffectImpl *This = (LinuxInputEffectImpl *)iface;
	TRACE("(this=%p)\n", This);

	/* Erase the downloaded effect */
	if (ioctl(*(This->fd), EVIOCRMFF, This->effect.id) == -1)
		return DIERR_INVALIDPARAM;

	/* Mark the effect as deallocated */
	This->effect.id = -1;

	return DI_OK;
}



static ULONG WINAPI LinuxInputEffectImpl_Release(LPDIRECTINPUTEFFECT iface)
{
	LinuxInputEffectImpl *This = (LinuxInputEffectImpl *)iface;
	ULONG ref = InterlockedDecrement(&(This->ref));

	if (ref == 0)
	{
		LinuxInputEffectImpl_Stop(iface);
		LinuxInputEffectImpl_Unload(iface);
		list_remove(This->entry);
		HeapFree(GetProcessHeap(), 0, LIST_ENTRY(This->entry, effect_list_item, entry));
		HeapFree(GetProcessHeap(), 0, This);
	}
	return ref;
}

/******************************************************************************
 *	  LinuxInputEffect
 */

HRESULT linuxinput_create_effect(int* fd,REFGUID rguid,	struct list *parent_list_entry, LPDIRECTINPUTEFFECT* peff)
{
	LinuxInputEffectImpl* newEffect = HeapAlloc(GetProcessHeap(), 
	HEAP_ZERO_MEMORY, sizeof(LinuxInputEffectImpl));
	DWORD type = _typeFromGUID(rguid);

	newEffect->lpVtbl 	= &LinuxInputEffectVtbl;
	newEffect->ref 		= 1;
	newEffect->guid 	= *rguid;
	newEffect->fd 		= fd;
	newEffect->gain 	= 0xFFFF;

	/* set the type.  this cannot be changed over the effect's life. */
	switch (type) {
		case DIEFT_PERIODIC: 
			newEffect->effect.type = FF_PERIODIC;
			if (IsEqualGUID(rguid, &GUID_Sine)) {
				newEffect->effect.u.periodic.waveform = FF_SINE;
			} else if (IsEqualGUID(rguid, &GUID_Triangle)) {
				newEffect->effect.u.periodic.waveform = FF_TRIANGLE;
			} else if (IsEqualGUID(rguid, &GUID_Square)) {
				newEffect->effect.u.periodic.waveform = FF_SQUARE;
			} else if (IsEqualGUID(rguid, &GUID_SawtoothUp)) {
				newEffect->effect.u.periodic.waveform = FF_SAW_UP;
			} else if (IsEqualGUID(rguid, &GUID_SawtoothDown)) {
				newEffect->effect.u.periodic.waveform = FF_SAW_DOWN;
			}
			break;
		case DIEFT_CONSTANTFORCE: 
				newEffect->effect.type = FF_CONSTANT;
			break;
		case DIEFT_RAMPFORCE: 
			newEffect->effect.type = FF_RAMP;
			break;
		case DIEFT_CONDITION: 
			if (IsEqualGUID(rguid, &GUID_Spring)) {
				newEffect->effect.type = FF_SPRING;
			} else if (IsEqualGUID(rguid, &GUID_Friction)) {
				newEffect->effect.type = FF_FRICTION;
			} else if (IsEqualGUID(rguid, &GUID_Inertia)) {
				newEffect->effect.type = FF_INERTIA;
			} else if (IsEqualGUID(rguid, &GUID_Damper)) {
				newEffect->effect.type = FF_DAMPER;
			}
			break;
		case DIEFT_CUSTOMFORCE:
			FIXME("Custom forces are not supported.\n");
			HeapFree(GetProcessHeap(), 0, newEffect);
			return DIERR_INVALIDPARAM;
		default:
			FIXME("Unknown force type 0x%x.\n", type);
			HeapFree(GetProcessHeap(), 0, newEffect);
			return DIERR_INVALIDPARAM;
	}

	/* mark as non-uploaded */
	newEffect->effect.id = -1;

	newEffect->entry = parent_list_entry;

	*peff = (LPDIRECTINPUTEFFECT)newEffect; 

	TRACE("Creating linux input system effect (%p) with guid %s\n", 
	  *peff, _dump_dinput_GUID(rguid));

	return DI_OK;
}



HRESULT linuxinput_get_info_A(int fd, REFGUID rguid, LPDIEFFECTINFOA info)
{
	DWORD type = _typeFromGUID(rguid);

	TRACE("(%d, %s, %p) type=%d\n", fd, _dump_dinput_GUID(rguid), info, type);

	if (!info) return E_POINTER;

	if (info->dwSize != sizeof(DIEFFECTINFOA)) return DIERR_INVALIDPARAM;

	info->guid = *rguid;
	
	info->dwEffType = type; 
	/* the event device API does not support querying for all these things
	 * therefore we assume that we have support for them
	 * that's not as dangerous as it sounds, since drivers are allowed to
	 * ignore parameters they claim to support anyway */
	info->dwEffType |= DIEFT_DEADBAND | DIEFT_FFATTACK | DIEFT_FFFADE | DIEFT_POSNEGCOEFFICIENTS | DIEFT_POSNEGSATURATION | DIEFT_SATURATION | DIEFT_STARTDELAY; 

	/* again, assume we have support for everything */
	info->dwStaticParams = DIEP_ALLPARAMS;
	info->dwDynamicParams = info->dwStaticParams;

	/* yes, this is windows behavior (print the GUID_Name for name) */
	strcpy(info->tszName, _dump_dinput_GUID(rguid));

	return DI_OK;
}



HRESULT linuxinput_get_info_W(int fd, REFGUID rguid, LPDIEFFECTINFOW info)
{
	DWORD type = _typeFromGUID(rguid);

	TRACE("(%d, %s, %p) type=%d\n", fd, _dump_dinput_GUID(rguid), info, type);

	if (!info) return E_POINTER;

	if (info->dwSize != sizeof(DIEFFECTINFOW)) return DIERR_INVALIDPARAM;

	info->guid = *rguid;

	info->dwEffType = type;
	/* the event device API does not support querying for all these things
	 * therefore we assume that we have support for them
	 * that's not as dangerous as it sounds, since drivers are allowed to
	 * ignore parameters they claim to support anyway */
	info->dwEffType |= DIEFT_DEADBAND | DIEFT_FFATTACK | DIEFT_FFFADE | DIEFT_POSNEGCOEFFICIENTS | DIEFT_POSNEGSATURATION | DIEFT_SATURATION | DIEFT_STARTDELAY; 

	/* again, assume we have support for everything */
	info->dwStaticParams = DIEP_ALLPARAMS;
	info->dwDynamicParams = info->dwStaticParams;

	/* yes, this is windows behavior (print the GUID_Name for name) */
	MultiByteToWideChar(CP_ACP, 0, _dump_dinput_GUID(rguid), -1, 
						info->tszName, MAX_PATH);

	return DI_OK;
}



static const IDirectInputEffectVtbl LinuxInputEffectVtbl = {
	LinuxInputEffectImpl_QueryInterface,
	LinuxInputEffectImpl_AddRef,
	LinuxInputEffectImpl_Release,
	LinuxInputEffectImpl_Initialize,
	LinuxInputEffectImpl_GetEffectGuid,
	LinuxInputEffectImpl_GetParameters,
	LinuxInputEffectImpl_SetParameters,
	LinuxInputEffectImpl_Start,
	LinuxInputEffectImpl_Stop,
	LinuxInputEffectImpl_GetEffectStatus,
	LinuxInputEffectImpl_Download,
	LinuxInputEffectImpl_Unload,
	LinuxInputEffectImpl_Escape
};

#endif /* HAVE_STRUCT_FF_EFFECT_DIRECTION */
/*
 * ffcfstress.c
 *
 * Force Feedback: Constant Force Stress Test
 *
 * Copyright (C) 2001 Oliver Hamann
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */

#include <linux/input.h>
#include <sys/ioctl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>


/* Helper for testing large bit masks */
#define TEST_BIT(bit,bits) (((bits[bit>>5]>>(bit&0x1f))&1)!=0)


/* Default values for the options */
#define DEFAULT_DEVICE_NAME "/dev/input/event0"
#define DEFAULT_UPDATE_RATE      25.0
#define DEFAULT_MOTION_FREQUENCY  0.5
#define DEFAULT_MOTION_AMPLITUDE  1.0
#define DEFAULT_SPRING_STRENGTH   1.0


/* Options */
const char * device_name = DEFAULT_DEVICE_NAME;
double update_rate       = DEFAULT_UPDATE_RATE;
double motion_frequency  = DEFAULT_MOTION_FREQUENCY;
double motion_amplitude  = DEFAULT_MOTION_AMPLITUDE;
double spring_strength   = DEFAULT_SPRING_STRENGTH;
int stop_and_play = 0;  /* Stop-upload-play effects instead of updating */


/* Global variables about the initialized device */
int device_handle;
int axis_code, axis_min, axis_max;
struct ff_effect effect;


/* Parse command line arguments */
void parse_args(int argc, char * argv[])
{
	int i;

	if (argc<2) goto l_help;

	for (i=1; i<argc; i++) {
		if      (!strcmp(argv[i],"-d") && i<argc-1) device_name     =argv[++i];
		else if (!strcmp(argv[i],"-u") && i<argc-1) update_rate     =atof(argv[++i]);
		else if (!strcmp(argv[i],"-f") && i<argc-1) motion_frequency=atof(argv[++i]);
		else if (!strcmp(argv[i],"-a") && i<argc-1) motion_amplitude=atof(argv[++i]);
		else if (!strcmp(argv[i],"-s") && i<argc-1) spring_strength =atof(argv[++i]);
		else if (!strcmp(argv[i],"-o")) ;
		else goto l_help;
	}
	return;

l_help:
	printf("-------- ffcfstress - Force Feedback: Constant Force Stress Test --------\n");
	printf("Description:\n");
	printf("  This program is for stress testing constant non-enveloped forces on\n");
	printf("  a force feedback device via the event interface. It simulates a\n");
	printf("  moving spring force by a frequently updated constant force effect.\n");
	printf("  BE CAREFUL IN USAGE, YOUR DEVICE MAY GET DAMAGED BY THE STRESS TEST!\n");
	printf("Usage:\n");
	printf("  %s <option> [<option>...]\n",argv[0]);
	printf("Options:\n");
	printf("  -d <string>  device name (default: %s)\n",DEFAULT_DEVICE_NAME);
	printf("  -u <double>  update rate in Hz (default: %.2f)\n",DEFAULT_UPDATE_RATE);
	printf("  -f <double>  spring center motion frequency in Hz (default: %.2f)\n",DEFAULT_MOTION_FREQUENCY);
	printf("  -a <double>  spring center motion amplitude 0.0..1.0 (default: %.2f)\n",DEFAULT_MOTION_AMPLITUDE);
	printf("  -s <double>  spring strength factor (default: %.2f)\n",DEFAULT_SPRING_STRENGTH);
	printf("  -o           dummy option (useful because at least one option is needed)\n");
	exit(1);
}


/* Initialize device, create constant force effect */
void init_device()
{
	unsigned long key_bits[32],abs_bits[32],ff_bits[32];
	struct input_event event;
	int valbuf[16];

	/* Open event device with write permission */
	device_handle = open(device_name,O_RDWR|O_NONBLOCK);
	if (device_handle<0) {
		fprintf(stderr,"ERROR: can not open %s (%s) [%s:%d]\n",
		        device_name,strerror(errno),__FILE__,__LINE__);
		exit(1);
	}

	/* Which buttons has the device? */
	memset(key_bits,0,32*sizeof(unsigned long));
	if (ioctl(device_handle,EVIOCGBIT(EV_KEY,32*sizeof(unsigned long)),key_bits)<0) {
		fprintf(stderr,"ERROR: can not get key bits (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}

	/* Which axes has the device? */
	memset(abs_bits,0,32*sizeof(unsigned long));
	if (ioctl(device_handle,EVIOCGBIT(EV_ABS,32*sizeof(unsigned long)),abs_bits)<0) {
		fprintf(stderr,"ERROR: can not get abs bits (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}

	/* Now get some information about force feedback */
	memset(ff_bits,0,32*sizeof(unsigned long));
	if (ioctl(device_handle,EVIOCGBIT(EV_FF ,32*sizeof(unsigned long)),ff_bits)<0) {
		fprintf(stderr,"ERROR: can not get ff bits (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}

	/* Which axis is the x-axis? */
	if      (TEST_BIT(ABS_X    ,abs_bits)) axis_code=ABS_X;
	else if (TEST_BIT(ABS_RX   ,abs_bits)) axis_code=ABS_RX;
	else if (TEST_BIT(ABS_WHEEL,abs_bits)) axis_code=ABS_WHEEL;
	else {
		fprintf(stderr,"ERROR: no suitable x-axis found [%s:%d]\n",
		        __FILE__,__LINE__);
		exit(1);
	}

	/* get axis value range */
	if (ioctl(device_handle,EVIOCGABS(axis_code),valbuf)<0) {
		fprintf(stderr,"ERROR: can not get axis value range (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}
	axis_min=valbuf[1];
	axis_max=valbuf[2];
	if (axis_min>=axis_max) {
		fprintf(stderr,"ERROR: bad axis value range (%d,%d) [%s:%d]\n",
		        axis_min,axis_max,__FILE__,__LINE__);
		exit(1);
	}

	/* force feedback supported? */
	if (!TEST_BIT(FF_CONSTANT,ff_bits)) {
		fprintf(stderr,"ERROR: device (or driver) has no force feedback support [%s:%d]\n",
		        __FILE__,__LINE__);
		exit(1);
	}

	/* Switch off auto centering */
	memset(&event,0,sizeof(event));
	event.type=EV_FF;
	event.code=FF_AUTOCENTER;
	event.value=0;
	if (write(device_handle,&event,sizeof(event))!=sizeof(event)) {
		fprintf(stderr,"ERROR: failed to disable auto centering (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}

	/* Initialize constant force effect */
	memset(&effect,0,sizeof(effect));
	effect.type=FF_CONSTANT;
	effect.id=-1;
	effect.trigger.button=0;
	effect.trigger.interval=0;
	effect.replay.length=0xffff;
	effect.replay.delay=0;
	effect.u.constant.level=0;
	effect.direction=0x7f00;
	effect.u.constant.envelope.attack_length=0;
	effect.u.constant.envelope.attack_level=0;
	effect.u.constant.envelope.fade_length=0;
	effect.u.constant.envelope.fade_level=0;

	/* Upload effect */
	if (ioctl(device_handle,EVIOCSFF,&effect)==-1) {
		fprintf(stderr,"ERROR: uploading effect failed (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}

	/* Start effect */
	memset(&event,0,sizeof(event));
	event.type=EV_FF;
	event.code=effect.id;
	event.value=1;
	if (write(device_handle,&event,sizeof(event))!=sizeof(event)) {
		fprintf(stderr,"ERROR: starting effect failed (%s) [%s:%d]\n",
		        strerror(errno),__FILE__,__LINE__);
		exit(1);
	}
}


/* update the device: set force and query joystick position */
void update_device(double force, double * position)
{
	struct input_event event;

	/* Delete effect */
	if (stop_and_play && effect.id!=-1) {
		if (ioctl(device_handle,EVIOCRMFF,effect.id)==-1) {
			fprintf(stderr,"ERROR: removing effect failed (%s) [%s:%d]\n",
			        strerror(errno),__FILE__,__LINE__);
			exit(1);
		}
		effect.id=-1;
	}

	/* Set force */
	if (force>1.0) force=1.0;
	if (force<-1.0) force=-1.0;
	effect.u.constant.level=(short)(force*32767.0); /* only to be safe */
	effect.direction=0xC000; 
/*	effect.u.ramp.start_level=(short)(force*127); 	/*  */
/*	effect.u.ramp.end_level	 =(short)(force*127); 	/*  */
	effect.u.constant.envelope.attack_level=-16656;/*(short)(force*32767.0); /* this one counts! */
	effect.u.constant.envelope.fade_level=(short)(force*32767.0); /* only to be safe */

	fprintf(stdout," Force: %d ", 		(short)(force*32767.0));
	fprintf(stdout," start_level: %d ", 	effect.u.ramp.start_level);
	fprintf(stdout," end_level: %d ", 	effect.u.ramp.end_level);

	/* Upload effect */
	if (ioctl(device_handle,EVIOCSFF,&effect)==-1) {
		perror("upload effect");
		/* We do not exit here. Indeed, too frequent updates may be
		 * refused, but that is not a fatal error */
	}

	/* Start effect */
	if (stop_and_play && effect.id!=-1) {
		memset(&event,0,sizeof(event));
		event.type=EV_FF;
		event.code=effect.id;
		event.value=1;
		if (write(device_handle,&event,sizeof(event))!=sizeof(event)) {
			fprintf(stderr,"ERROR: re-starting effect failed (%s) [%s:%d]\n", strerror(errno),__FILE__,__LINE__);
			exit(1);
		}
	}

	/* Get events */
	while (read(device_handle,&event,sizeof(event))==sizeof(event)) 
	{
		if (event.type==EV_ABS && event.code==axis_code) 
		{
			*position=((double)(((short)event.value)-axis_min))*2.0/(axis_max-axis_min)-1.0;
			if (*position>1.0) 
				*position=1.0;
			else if (*position<-1.0) 
				*position=-1.0;
		}
	}
}


/* little helper to print a graph bar from a value */
void fprint_bar(FILE * file, double value, int radius)
{
	int i,c;

	for (i=0; i<radius*2+1; i++) {
		if (i==radius) c='|';
		else if ((i<radius && value*radius<i-radius+0.25) ||
		         (i>radius && value*radius>i-radius-0.25)) c='*';
		else if ((i<radius && value*radius<i-radius+0.75) ||
		         (i>radius && value*radius>i-radius-0.75)) c='+';
		else if (i==0) c='<';
		else if (i==radius*2) c='>';
		else c='-';
		fputc(c,file);
	}
}


/* main: perform the spring simulation */
int main(int argc, char * argv[])
{
	double time,position,center,force;

	/* Parse command line arguments */
	parse_args(argc,argv);

	/* Initialize device, create constant force effect */
	init_device();

	/* Print header */
	printf("\n        position                   center                     force\n");

	/* For ever */
	for (position=0, time=0;; time+=1.0/update_rate) {

		/* Spring center oscillates (no more) */
		center = 0;		/* sin( 2 * M_PI * motion_frequency ) * motion_amplitude;*/

		/* Calculate spring force */
		force = ( center - position ) * spring_strength;
		if (force >  1.0) force =  1.0;
		if (force < -1.0) force = -1.0;

		/* Print graph bars */
		printf("\r");
		fprint_bar(stdout,position,12);
		printf(" ");
		fprint_bar(stdout,center,12);
		printf(" ");
		fprint_bar(stdout,force,12);
		fflush(stdout);

		/* Set force and ask for joystick position */
		update_device(force,&position);

		/* Next time... */
		usleep((unsigned long)(1000000.0/update_rate));

	}
}

Attachment: signature.asc
Description: This is a digitally signed message part


[Index of Archives]     [Linux Media Devel]     [Linux USB Devel]     [Video for Linux]     [Linux Audio Users]     [Yosemite News]     [Linux Kernel]     [Linux SCSI]     [Linux Wireless Networking]     [Linux Omap]

  Powered by Linux