-- v2: windows.gaming.input: Assume that joysticks with single FFB axis are racing wheels. windows.gaming.input: Implement the number of FFB axes according to the SupportedAxes property. dinput/tests: Reduce the available FFB axes to X and Y in test_windows_gaming_input. windows.gaming.input: Set initial effect parameters within the CS. windows.gaming.input: Implement IForceFeedbackMotor_get_SupportedAxes. dinput: Always send both "Type Specific" and "Set Effect" reports on initial Download. dinput/tests: Test zero-ed force feedback effect parameters.
From: Ivo Ivanov logos128@gmail.com
Fixes FH5 not having (or having very subtle) force feedback. Potentially affects most WGI games, since the constant effect is usually the main effect. --- dlls/windows.gaming.input/constant_effect.c | 1 + dlls/windows.gaming.input/ramp_effect.c | 1 + 2 files changed, 2 insertions(+)
diff --git a/dlls/windows.gaming.input/constant_effect.c b/dlls/windows.gaming.input/constant_effect.c index 0af5d736f48..15763b30d67 100644 --- a/dlls/windows.gaming.input/constant_effect.c +++ b/dlls/windows.gaming.input/constant_effect.c @@ -107,6 +107,7 @@ static HRESULT WINAPI effect_SetParameters( IConstantForceEffect *iface, Vector3 .direction = direction, .duration = duration, .repeat_count = 1, + .gain = 1., }, }; struct constant_effect *impl = impl_from_IConstantForceEffect( iface ); diff --git a/dlls/windows.gaming.input/ramp_effect.c b/dlls/windows.gaming.input/ramp_effect.c index a498942cabe..fadcf151c04 100644 --- a/dlls/windows.gaming.input/ramp_effect.c +++ b/dlls/windows.gaming.input/ramp_effect.c @@ -108,6 +108,7 @@ static HRESULT WINAPI effect_SetParameters( IRampForceEffect *iface, Vector3 sta .end_vector = end_vector, .duration = duration, .repeat_count = 1, + .gain = 1., }, }; struct ramp_effect *impl = impl_from_IRampForceEffect( iface );
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/dinput/tests/force_feedback.c | 91 ++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+)
diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index a2ccdb08350..d9ae039f681 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -359,6 +359,50 @@ static void test_periodic_effect( IDirectInputDevice8W *device, HANDLE file, DWO .report_buf = {0x02,0x01,0x01,0x01}, }, }; + struct hid_expect expect_download_0[] = + { + /* set periodic */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 5, + .report_len = 2, + .report_buf = {0x05,0x00}, + .todo = TRUE, + }, + /* set envelope */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 6, + .report_len = 7, + .report_buf = {0x06,0x00,0x00,0x00,0x00,0x00,0x00}, + .todo = TRUE, + }, + /* update effect */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 3, + .report_len = 11, + .report_buf = {0x03,0x01,0x02,0x08,0x01,0x00,0x00,0x00,0x01,0x3f,0x00}, + }, + }; + struct hid_expect expect_download_1[] = + { + /* set periodic */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 5, + .report_len = 2, + .report_buf = {0x05,0x00}, + .todo = TRUE, + }, + /* update effect */ + { + .code = IOCTL_HID_WRITE_REPORT, + .report_id = 3, + .report_len = 11, + .report_buf = {0x03,0x01,0x02,0x08,0x01,0x00,0x00,0x00,0x01,0x3f,0x00}, + }, + }; struct hid_expect expect_download_2[] = { /* set periodic */ @@ -529,6 +573,22 @@ static void test_periodic_effect( IDirectInputDevice8W *device, HANDLE file, DWO .lpvTypeSpecificParams = (void *)&expect_periodic, .dwStartDelay = 6000, }; + static const LONG expect_directions_0[3] = {0}; + static const DIENVELOPE expect_envelope_0 = {.dwSize = sizeof(DIENVELOPE)}; + static const DIPERIODIC expect_periodic_0 = {0}; + const DIEFFECT expect_desc_0 = + { + .dwSize = version >= 0x700 ? sizeof(DIEFFECT_DX6) : sizeof(DIEFFECT_DX5), + .dwFlags = DIEFF_SPHERICAL | DIEFF_OBJECTIDS, + .dwDuration = 1000, + .dwTriggerButton = DIDFT_PSHBUTTON | DIDFT_MAKEINSTANCE( 0 ) | DIDFT_FFEFFECTTRIGGER, + .cAxes = 3, + .rgdwAxes = (void *)expect_axes, + .rglDirection = (void *)expect_directions_0, + .lpEnvelope = (void *)&expect_envelope_0, + .cbTypeSpecificParams = sizeof(DIPERIODIC), + .lpvTypeSpecificParams = (void *)&expect_periodic_0, + }; struct check_created_effect_params check_params = {0}; IDirectInputEffect *effect; DIPERIODIC periodic = {0}; @@ -1315,9 +1375,40 @@ static void test_periodic_effect( IDirectInputDevice8W *device, HANDLE file, DWO winetest_pop_context(); }
+ /* zero-ed effect parameters are sent */ + + hr = IDirectInputDevice8_CreateEffect( device, &GUID_Sine, NULL, &effect, NULL ); + ok( hr == DI_OK, "CreateEffect returned %#lx\n", hr ); + + set_hid_expect( file, expect_download_0, sizeof(expect_download_0) ); + flags = version >= 0x700 ? DIEP_ALLPARAMS : DIEP_ALLPARAMS_DX5; + hr = IDirectInputEffect_SetParameters( effect, &expect_desc_0, flags ); + ok( hr == DI_OK, "SetParameters returned %#lx\n", hr ); + set_hid_expect( file, NULL, 0 ); + + set_hid_expect( file, &expect_stop, sizeof(expect_stop) ); + ref = IDirectInputEffect_Release( effect ); + ok( ref == 0, "Release returned %ld\n", ref ); + set_hid_expect( file, NULL, 0 ); + + hr = IDirectInputDevice8_CreateEffect( device, &GUID_Sine, NULL, &effect, NULL ); + ok( hr == DI_OK, "CreateEffect returned %#lx\n", hr ); + + set_hid_expect( file, expect_download_1, sizeof(expect_download_1) ); + flags = version >= 0x700 ? DIEP_ALLPARAMS : DIEP_ALLPARAMS_DX5; + hr = IDirectInputEffect_SetParameters( effect, &expect_desc_0, (flags & ~DIEP_ENVELOPE) ); + ok( hr == DI_OK, "SetParameters returned %#lx\n", hr ); + set_hid_expect( file, NULL, 0 ); + + set_hid_expect( file, &expect_stop, sizeof(expect_stop) ); + ref = IDirectInputEffect_Release( effect ); + ok( ref == 0, "Release returned %ld\n", ref ); + set_hid_expect( file, NULL, 0 ); + hr = IDirectInputDevice8_CreateEffect( device, &GUID_Sine, NULL, &effect, NULL ); ok( hr == DI_OK, "CreateEffect returned %#lx\n", hr );
+ set_hid_expect( file, expect_download_2, sizeof(expect_download_2) ); flags = version >= 0x700 ? DIEP_ALLPARAMS : DIEP_ALLPARAMS_DX5; hr = IDirectInputEffect_SetParameters( effect, &expect_desc, flags );
From: Ivo Ivanov logos128@gmail.com
Fixes the Download method not sending an initial type specific HID PID report in the rare cases where all type specific params are set to 0 through the initial SetParameters call, so they aren't considered as modified.
FH5 is affected by this issue, since it initially sets the direction of its constant effect to 0, which translates to 0 magnitude in dinput. --- dlls/dinput/joystick_hid.c | 4 +++- dlls/dinput/tests/force_feedback.c | 3 --- 2 files changed, 3 insertions(+), 4 deletions(-)
diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 2065fe429eb..311e5903878 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -2993,6 +2993,7 @@ static HRESULT WINAPI hid_joystick_effect_Download( IDirectInputEffect *iface ) case PID_USAGE_ET_SAWTOOTH_DOWN: case PID_USAGE_ET_CONSTANT_FORCE: case PID_USAGE_ET_RAMP: + if (!(impl->flags & DIEP_ENVELOPE)) break; if (!(impl->modified & DIEP_ENVELOPE)) break;
set_parameter_value( impl, impl->set_envelope_buf, set_envelope->attack_level_caps, @@ -3100,7 +3101,7 @@ static HRESULT WINAPI hid_joystick_effect_Unload( IDirectInputEffect *iface ) else hr = DIERR_INPUTLOST; }
- impl->modified = impl->flags; + impl->modified = ~0; impl->index = 0; check_empty_force_feedback_state( joystick ); } @@ -3161,6 +3162,7 @@ static HRESULT hid_joystick_create_effect( IDirectInputDevice8W *iface, IDirectI impl->params.rgdwAxes = impl->axes; impl->params.rglDirection = impl->directions; impl->params.dwTriggerButton = -1; + impl->modified = ~0; impl->status = 0;
*out = &impl->IDirectInputEffect_iface; diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index d9ae039f681..de7d5990f34 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -367,7 +367,6 @@ static void test_periodic_effect( IDirectInputDevice8W *device, HANDLE file, DWO .report_id = 5, .report_len = 2, .report_buf = {0x05,0x00}, - .todo = TRUE, }, /* set envelope */ { @@ -375,7 +374,6 @@ static void test_periodic_effect( IDirectInputDevice8W *device, HANDLE file, DWO .report_id = 6, .report_len = 7, .report_buf = {0x06,0x00,0x00,0x00,0x00,0x00,0x00}, - .todo = TRUE, }, /* update effect */ { @@ -393,7 +391,6 @@ static void test_periodic_effect( IDirectInputDevice8W *device, HANDLE file, DWO .report_id = 5, .report_len = 2, .report_buf = {0x05,0x00}, - .todo = TRUE, }, /* update effect */ {
From: Ivo Ivanov logos128@gmail.com
--- dlls/dinput/tests/force_feedback.c | 13 +++++++---- dlls/windows.gaming.input/force_feedback.c | 26 ++++++++++++++++++++-- 2 files changed, 33 insertions(+), 6 deletions(-)
diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index de7d5990f34..d743f959f55 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -5735,10 +5735,10 @@ static void test_windows_gaming_input(void) EventRegistrationToken controller_added_token; IPeriodicForceEffectFactory *periodic_factory; struct bool_async_handler bool_async_handler; + ForceFeedbackEffectAxes supported_axes, axes; IVectorView_ForceFeedbackMotor *motors_view; IConditionForceEffect *condition_effect; ConditionForceEffectKind condition_kind; - ForceFeedbackEffectAxes supported_axes; IActivationFactory *activation_factory; IPeriodicForceEffect *periodic_effect; IConstantForceEffect *constant_effect; @@ -5856,12 +5856,17 @@ static void test_windows_gaming_input(void) ok( hr == S_OK, "get_IsEnabled returned %#lx\n", hr ); ok( enabled == TRUE, "got enabled %u\n", enabled );
+ /* SupportedAxes always returns ForceFeedbackEffectAxes_X on Windows, + * no matter which axis is available for FFB in the Set Effects report, + * or whether a X axis is declared at all. + */ + supported_axes = 0xdeadbeef; hr = IForceFeedbackMotor_get_SupportedAxes( motor, &supported_axes ); - todo_wine ok( hr == S_OK, "get_SupportedAxes returned %#lx\n", hr ); - todo_wine - ok( supported_axes == ForceFeedbackEffectAxes_X, "got axes %#x\n", supported_axes ); + axes = ForceFeedbackEffectAxes_X | ForceFeedbackEffectAxes_Y | ForceFeedbackEffectAxes_Z; + ok( supported_axes == axes || broken( supported_axes == ForceFeedbackEffectAxes_X ), + "got axes %#x\n", supported_axes );
set_hid_expect( file, &expect_pause, sizeof(expect_pause) ); hr = IForceFeedbackMotor_PauseAllEffects( motor ); diff --git a/dlls/windows.gaming.input/force_feedback.c b/dlls/windows.gaming.input/force_feedback.c index 198268eac24..e32e6fea9b4 100644 --- a/dlls/windows.gaming.input/force_feedback.c +++ b/dlls/windows.gaming.input/force_feedback.c @@ -532,10 +532,32 @@ static HRESULT WINAPI motor_get_IsEnabled( IForceFeedbackMotor *iface, BOOLEAN * return hr; }
+static BOOL CALLBACK check_ffb_axes( const DIDEVICEOBJECTINSTANCEW *obj, void *args ) +{ + ForceFeedbackEffectAxes *value = args; + + if (obj->dwType & DIDFT_FFACTUATOR) + { + if (IsEqualIID( &obj->guidType, &GUID_XAxis )) *value |= ForceFeedbackEffectAxes_X; + else if (IsEqualIID( &obj->guidType, &GUID_YAxis )) *value |= ForceFeedbackEffectAxes_Y; + else if (IsEqualIID( &obj->guidType, &GUID_ZAxis )) *value |= ForceFeedbackEffectAxes_Z; + } + + return DIENUM_CONTINUE; +} + static HRESULT WINAPI motor_get_SupportedAxes( IForceFeedbackMotor *iface, enum ForceFeedbackEffectAxes *value ) { - FIXME( "iface %p, value %p stub!\n", iface, value ); - return E_NOTIMPL; + struct motor *impl = impl_from_IForceFeedbackMotor( iface ); + HRESULT hr; + + TRACE( "iface %p, value %p.\n", iface, value ); + + *value = ForceFeedbackEffectAxes_None; + if (FAILED(hr = IDirectInputDevice8_EnumObjects( impl->device, check_ffb_axes, value, DIDFT_AXIS ))) + *value = ForceFeedbackEffectAxes_None; + + return hr; }
static HRESULT WINAPI motor_load_effect_async( IUnknown *invoker, IUnknown *param, PROPVARIANT *result )
From: Rémi Bernon rbernon@codeweavers.com
--- dlls/windows.gaming.input/force_feedback.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-)
diff --git a/dlls/windows.gaming.input/force_feedback.c b/dlls/windows.gaming.input/force_feedback.c index e32e6fea9b4..f24f0627d6f 100644 --- a/dlls/windows.gaming.input/force_feedback.c +++ b/dlls/windows.gaming.input/force_feedback.c @@ -567,17 +567,15 @@ static HRESULT WINAPI motor_load_effect_async( IUnknown *invoker, IUnknown *para IDirectInputEffect *dinput_effect; HRESULT hr;
- if (SUCCEEDED(hr = IDirectInputDevice8_CreateEffect( impl->device, &effect->type, &effect->params, &dinput_effect, NULL ))) + EnterCriticalSection( &effect->cs ); + if (SUCCEEDED(hr = IDirectInputDevice8_CreateEffect( impl->device, &effect->type, &effect->params, + &dinput_effect, NULL ))) { - EnterCriticalSection( &effect->cs ); - if (!effect->effect) - { - effect->effect = dinput_effect; - IDirectInputEffect_AddRef( effect->effect ); - } - LeaveCriticalSection( &effect->cs ); - IDirectInputEffect_Release( dinput_effect ); + if (effect->effect) IDirectInputEffect_Release( effect->effect ); + effect->effect = dinput_effect; + IDirectInputEffect_AddRef( effect->effect ); } + LeaveCriticalSection( &effect->cs );
result->vt = VT_UI4; if (SUCCEEDED(hr)) result->ulVal = ForceFeedbackLoadEffectResult_Succeeded;
From: Ivo Ivanov logos128@gmail.com
Windows.Gaming.Input on Windows always uses the X and Y axes for FFB, ignoring what is declared in the HID report descriptor, and the Axes Enable collection. Since we have the correct behavior on Wine, this allows the test to complete on both platforms without issues. --- dlls/dinput/tests/force_feedback.c | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-)
diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index d743f959f55..b841e4bb8c7 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -4967,19 +4967,18 @@ static void test_windows_gaming_input(void) COLLECTION(1, Logical), USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_X), USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_Y), - USAGE(4, (HID_USAGE_PAGE_GENERIC << 16)|HID_USAGE_GENERIC_Z), LOGICAL_MINIMUM(1, 0), LOGICAL_MAXIMUM(1, 1), PHYSICAL_MINIMUM(1, 0), PHYSICAL_MAXIMUM(1, 1), REPORT_SIZE(1, 1), - REPORT_COUNT(1, 3), + REPORT_COUNT(1, 2), OUTPUT(1, Data|Var|Abs), END_COLLECTION, USAGE(1, PID_USAGE_DIRECTION_ENABLE), REPORT_COUNT(1, 1), OUTPUT(1, Data|Var|Abs), - REPORT_COUNT(1, 4), + REPORT_COUNT(1, 5), OUTPUT(1, Cnst|Var|Abs),
USAGE(1, PID_USAGE_DURATION), @@ -5566,7 +5565,7 @@ static void test_windows_gaming_input(void) .code = IOCTL_HID_WRITE_REPORT, .report_id = 3, .report_len = 18, - .report_buf = {3,0x01,0x02,0x08,0x78,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0xff,0xff,0x4e,0x01,0x00,0x00}, + .report_buf = {3,0x01,0x02,0x04,0x78,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0xff,0xff,0x4e,0x01,0x00,0x00}, }, }; struct hid_expect expect_create_condition[] = @@ -5597,7 +5596,7 @@ static void test_windows_gaming_input(void) .code = IOCTL_HID_WRITE_REPORT, .report_id = 3, .report_len = 18, - .report_buf = {3,0x01,0x03,0x08,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x99,0x00,0x00,0x00}, + .report_buf = {3,0x01,0x03,0x04,0xff,0xff,0x00,0x00,0x00,0x00,0x00,0x00,0xff,0xff,0x99,0x00,0x00,0x00}, }, }; struct hid_expect expect_create_constant[] = @@ -5635,7 +5634,7 @@ static void test_windows_gaming_input(void) .code = IOCTL_HID_WRITE_REPORT, .report_id = 3, .report_len = 18, - .report_buf = {3,0x01,0x04,0x08,0x5a,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0xff,0x7f,0x4e,0x01,0x00,0x00}, + .report_buf = {3,0x01,0x04,0x04,0x5a,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0xff,0x7f,0x4e,0x01,0x00,0x00}, }, }; struct hid_expect expect_create_ramp[] = @@ -5673,7 +5672,7 @@ static void test_windows_gaming_input(void) .code = IOCTL_HID_WRITE_REPORT, .report_id = 3, .report_len = 18, - .report_buf = {3,0x01,0x05,0x08,0x5a,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0xff,0xff,0x4e,0x01,0x00,0x00}, + .report_buf = {3,0x01,0x05,0x04,0x5a,0x00,0x00,0x00,0x00,0x00,0x0a,0x00,0xff,0xff,0x4e,0x01,0x00,0x00}, }, }; struct hid_expect expect_effect_start = @@ -5864,7 +5863,7 @@ static void test_windows_gaming_input(void) supported_axes = 0xdeadbeef; hr = IForceFeedbackMotor_get_SupportedAxes( motor, &supported_axes ); ok( hr == S_OK, "get_SupportedAxes returned %#lx\n", hr ); - axes = ForceFeedbackEffectAxes_X | ForceFeedbackEffectAxes_Y | ForceFeedbackEffectAxes_Z; + axes = ForceFeedbackEffectAxes_X | ForceFeedbackEffectAxes_Y; ok( supported_axes == axes || broken( supported_axes == ForceFeedbackEffectAxes_X ), "got axes %#x\n", supported_axes );
@@ -6170,6 +6169,13 @@ static void test_windows_gaming_input(void) ok( hr == S_OK, "SetParametersWithEnvelope returned %#lx\n", hr ); IPeriodicForceEffect_Release( periodic_effect );
+ /* Windows.Gaming.Input always uses the X and Y directions on Windows, + * ignoring what is declared in the Axes Enable collection at the + * Set Effects report, or even the existence of the axes in the HID + * report. It ignores the Z direction, at least on HID PID devices. + * DirectInput works properly in such cases on Windows. + */ + set_hid_expect( file, expect_create_periodic, sizeof(expect_create_periodic) ); hr = IForceFeedbackMotor_LoadEffectAsync( motor, effect, &result_async ); ok( hr == S_OK, "LoadEffectAsync returned %#lx\n", hr );
From: Ivo Ivanov logos128@gmail.com
--- dlls/windows.gaming.input/force_feedback.c | 62 +++++++++++++++++----- 1 file changed, 48 insertions(+), 14 deletions(-)
diff --git a/dlls/windows.gaming.input/force_feedback.c b/dlls/windows.gaming.input/force_feedback.c index f24f0627d6f..f7a233b46d4 100644 --- a/dlls/windows.gaming.input/force_feedback.c +++ b/dlls/windows.gaming.input/force_feedback.c @@ -113,6 +113,7 @@ static HRESULT WINAPI effect_impl_put_Parameters( IWineForceFeedbackEffectImpl * WineForceFeedbackEffectEnvelope *envelope ) { struct effect *impl = impl_from_IWineForceFeedbackEffectImpl( iface ); + DWORD count = 0; HRESULT hr;
TRACE( "iface %p, params %p, envelope %p.\n", iface, ¶ms, envelope ); @@ -125,9 +126,9 @@ static HRESULT WINAPI effect_impl_put_Parameters( IWineForceFeedbackEffectImpl * impl->constant_force.lMagnitude = round( params.constant.gain * params.constant.direction.X * 10000 ); impl->params.dwDuration = params.constant.duration.Duration / 10; impl->params.dwStartDelay = params.constant.start_delay.Duration / 10; - impl->directions[0] = round( -params.constant.direction.X * 10000 ); - impl->directions[1] = round( -params.constant.direction.Y * 10000 ); - impl->directions[2] = round( -params.constant.direction.Z * 10000 ); + if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( -params.constant.direction.X * 10000 ); + if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( -params.constant.direction.Y * 10000 ); + if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( -params.constant.direction.Z * 10000 ); break;
case WineForceFeedbackEffectType_Ramp: @@ -136,9 +137,9 @@ static HRESULT WINAPI effect_impl_put_Parameters( IWineForceFeedbackEffectImpl * impl->ramp_force.lEnd = round( params.ramp.gain * params.ramp.end_vector.X * 10000 ); impl->params.dwDuration = params.ramp.duration.Duration / 10; impl->params.dwStartDelay = params.ramp.start_delay.Duration / 10; - impl->directions[0] = round( -params.ramp.start_vector.X * 10000 ); - impl->directions[1] = round( -params.ramp.start_vector.Y * 10000 ); - impl->directions[2] = round( -params.ramp.start_vector.Z * 10000 ); + if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( -params.ramp.start_vector.X * 10000 ); + if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( -params.ramp.start_vector.Y * 10000 ); + if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( -params.ramp.start_vector.Z * 10000 ); break;
case WineForceFeedbackEffectType_Periodic_SineWave: @@ -153,9 +154,9 @@ static HRESULT WINAPI effect_impl_put_Parameters( IWineForceFeedbackEffectImpl * impl->periodic.lOffset = round( params.periodic.bias * 10000 ); impl->params.dwDuration = params.periodic.duration.Duration / 10; impl->params.dwStartDelay = params.periodic.start_delay.Duration / 10; - impl->directions[0] = round( -params.periodic.direction.X * 10000 ); - impl->directions[1] = round( -params.periodic.direction.Y * 10000 ); - impl->directions[2] = round( -params.periodic.direction.Z * 10000 ); + if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( -params.periodic.direction.X * 10000 ); + if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( -params.periodic.direction.Y * 10000 ); + if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( -params.periodic.direction.Z * 10000 ); break;
case WineForceFeedbackEffectType_Condition_Spring: @@ -171,9 +172,9 @@ static HRESULT WINAPI effect_impl_put_Parameters( IWineForceFeedbackEffectImpl * impl->condition.lOffset = round( params.condition.bias * 10000 ); impl->params.dwDuration = -1; impl->params.dwStartDelay = 0; - impl->directions[0] = round( params.condition.direction.X * 10000 ); - impl->directions[1] = round( params.condition.direction.Y * 10000 ); - impl->directions[2] = round( params.condition.direction.Z * 10000 ); + if (impl->axes[count] == DIJOFS_X) impl->directions[count++] = round( params.condition.direction.X * 10000 ); + if (impl->axes[count] == DIJOFS_Y) impl->directions[count++] = round( params.condition.direction.Y * 10000 ); + if (impl->axes[count] == DIJOFS_Z) impl->directions[count++] = round( params.condition.direction.Z * 10000 ); break; }
@@ -374,7 +375,7 @@ HRESULT force_feedback_effect_create( enum WineForceFeedbackEffectType type, IIn impl->params.dwTriggerButton = -1; impl->params.dwGain = 10000; impl->params.dwFlags = DIEFF_CARTESIAN|DIEFF_OBJECTOFFSETS; - impl->params.cAxes = 2; + impl->params.cAxes = -1; impl->axes[0] = DIJOFS_X; impl->axes[1] = DIJOFS_Y; impl->axes[2] = DIJOFS_Z; @@ -563,11 +564,43 @@ static HRESULT WINAPI motor_get_SupportedAxes( IForceFeedbackMotor *iface, enum static HRESULT WINAPI motor_load_effect_async( IUnknown *invoker, IUnknown *param, PROPVARIANT *result ) { struct effect *effect = impl_from_IForceFeedbackEffect( (IForceFeedbackEffect *)param ); - struct motor *impl = impl_from_IForceFeedbackMotor( (IForceFeedbackMotor *)invoker ); + IForceFeedbackMotor *motor = (IForceFeedbackMotor *)invoker; + struct motor *impl = impl_from_IForceFeedbackMotor( motor ); + ForceFeedbackEffectAxes supported_axes = 0; IDirectInputEffect *dinput_effect; HRESULT hr;
EnterCriticalSection( &effect->cs ); + + if (FAILED(hr = IForceFeedbackMotor_get_SupportedAxes( motor, &supported_axes ))) + { + WARN( "get_SupportedAxes for motor %p returned %#lx\n", motor, hr ); + effect->params.cAxes = 0; + } + else if (effect->params.cAxes == -1) + { + DWORD count = 0; + + /* initialize axis mapping and re-map directions that were set with the initial mapping */ + if (supported_axes & ForceFeedbackEffectAxes_X) + { + effect->directions[count] = effect->directions[0]; + effect->axes[count++] = DIJOFS_X; + } + if (supported_axes & ForceFeedbackEffectAxes_Y) + { + effect->directions[count] = effect->directions[1]; + effect->axes[count++] = DIJOFS_Y; + } + if (supported_axes & ForceFeedbackEffectAxes_Z) + { + effect->directions[count] = effect->directions[2]; + effect->axes[count++] = DIJOFS_Z; + } + + effect->params.cAxes = count; + } + if (SUCCEEDED(hr = IDirectInputDevice8_CreateEffect( impl->device, &effect->type, &effect->params, &dinput_effect, NULL ))) { @@ -575,6 +608,7 @@ static HRESULT WINAPI motor_load_effect_async( IUnknown *invoker, IUnknown *para effect->effect = dinput_effect; IDirectInputEffect_AddRef( effect->effect ); } + LeaveCriticalSection( &effect->cs );
result->vt = VT_UI4;
From: Ivo Ivanov logos128@gmail.com
The HID PID steering wheels always declare one force feedback axis, while the joysticks always have two or more. So it is safe to assume that joysticks with single FFB axis are racing wheels.
Fixes FH5 not having force feedback with a Simucube 2 steering wheel, when using the hidraw backend. --- dlls/windows.gaming.input/provider.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-)
diff --git a/dlls/windows.gaming.input/provider.c b/dlls/windows.gaming.input/provider.c index 39229a35c7d..6908d733a53 100644 --- a/dlls/windows.gaming.input/provider.c +++ b/dlls/windows.gaming.input/provider.c @@ -141,6 +141,13 @@ static HRESULT WINAPI wine_provider_GetTrustLevel( IWineGameControllerProvider * return E_NOTIMPL; }
+static BOOL CALLBACK count_ffb_axes( const DIDEVICEOBJECTINSTANCEW *obj, void *args ) +{ + DWORD *count = args; + if (obj->dwType & DIDFT_FFACTUATOR) (*count)++; + return DIENUM_CONTINUE; +} + static HRESULT WINAPI wine_provider_get_Type( IWineGameControllerProvider *iface, WineGameControllerType *value ) { struct provider *impl = impl_from_IWineGameControllerProvider( iface ); @@ -155,7 +162,14 @@ static HRESULT WINAPI wine_provider_get_Type( IWineGameControllerProvider *iface { case DI8DEVTYPE_DRIVING: *value = WineGameControllerType_RacingWheel; break; case DI8DEVTYPE_GAMEPAD: *value = WineGameControllerType_Gamepad; break; - default: *value = WineGameControllerType_Joystick; break; + default: + { + DWORD count = 0; + hr = IDirectInputDevice8_EnumObjects( impl->dinput_device, count_ffb_axes, &count, DIDFT_AXIS ); + if (SUCCEEDED(hr) && count == 1) *value = WineGameControllerType_RacingWheel; + else *value = WineGameControllerType_Joystick; + break; + } }
return S_OK;
Its done. :slight_smile:
This merge request was approved by Rémi Bernon.