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: 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. The "Set Envelope" report is an exception in this case, as the effect is not used when all params are set to 0.
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 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 2065fe429eb..ba8a1e959e7 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -2904,7 +2904,11 @@ static HRESULT WINAPI hid_joystick_effect_Download( IDirectInputEffect *iface ) status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_PID, 0, PID_USAGE_EFFECT_BLOCK_INDEX, impl->index, impl->joystick->preparsed, impl->effect_update_buf, report_len ); if (status != HIDP_STATUS_SUCCESS) hr = status; - else hr = DI_OK; + else + { + hr = DI_OK; + impl->modified |= impl->flags & ~DIEP_ENVELOPE; + } }
if (hr == DI_OK)
From: Ivo Ivanov logos128@gmail.com
--- dlls/dinput/tests/force_feedback.c | 11 +++++--- dlls/windows.gaming.input/force_feedback.c | 29 ++++++++++++++++++++-- 2 files changed, 34 insertions(+), 6 deletions(-)
diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index a2ccdb08350..122d11dbcdd 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -5650,7 +5650,7 @@ static void test_windows_gaming_input(void) IVectorView_ForceFeedbackMotor *motors_view; IConditionForceEffect *condition_effect; ConditionForceEffectKind condition_kind; - ForceFeedbackEffectAxes supported_axes; + ForceFeedbackEffectAxes supported_axes, axes; IActivationFactory *activation_factory; IPeriodicForceEffect *periodic_effect; IConstantForceEffect *constant_effect; @@ -5768,12 +5768,15 @@ 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..76800ef5c1f 100644 --- a/dlls/windows.gaming.input/force_feedback.c +++ b/dlls/windows.gaming.input/force_feedback.c @@ -532,10 +532,35 @@ 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 (*value == (ForceFeedbackEffectAxes_X | ForceFeedbackEffectAxes_Y | ForceFeedbackEffectAxes_Z)) ? + DIENUM_STOP : 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: Ivo Ivanov logos128@gmail.com
--- dlls/windows.gaming.input/force_feedback.c | 69 ++++++++++++++++++---- 1 file changed, 56 insertions(+), 13 deletions(-)
diff --git a/dlls/windows.gaming.input/force_feedback.c b/dlls/windows.gaming.input/force_feedback.c index 76800ef5c1f..e2f61b3fc27 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,12 @@ 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->params.cAxes > 0 && impl->axes[0] == DIJOFS_X) + impl->directions[count++] = round( -params.constant.direction.X * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Y) + impl->directions[count++] = round( -params.constant.direction.Y * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Z) + impl->directions[count] = round( -params.constant.direction.Z * 10000 ); break;
case WineForceFeedbackEffectType_Ramp: @@ -136,9 +140,12 @@ 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->params.cAxes > 0 && impl->axes[0] == DIJOFS_X) + impl->directions[count++] = round( -params.ramp.start_vector.X * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Y) + impl->directions[count++] = round( -params.ramp.start_vector.Y * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Z) + impl->directions[count] = round( -params.ramp.start_vector.Z * 10000 ); break;
case WineForceFeedbackEffectType_Periodic_SineWave: @@ -153,9 +160,12 @@ 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->params.cAxes > 0 && impl->axes[0] == DIJOFS_X) + impl->directions[count++] = round( -params.periodic.direction.X * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Y) + impl->directions[count++] = round( -params.periodic.direction.Y * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Z) + impl->directions[count] = round( -params.periodic.direction.Z * 10000 ); break;
case WineForceFeedbackEffectType_Condition_Spring: @@ -171,9 +181,12 @@ 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->params.cAxes > 0 && impl->axes[0] == DIJOFS_X) + impl->directions[count++] = round( params.condition.direction.X * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Y) + impl->directions[count++] = round( params.condition.direction.Y * 10000 ); + if (impl->params.cAxes > count && impl->axes[count] == DIJOFS_Z) + impl->directions[count] = round( params.condition.direction.Z * 10000 ); break; }
@@ -374,7 +387,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 = 3; impl->axes[0] = DIJOFS_X; impl->axes[1] = DIJOFS_Y; impl->axes[2] = DIJOFS_Z; @@ -593,7 +606,37 @@ static HRESULT WINAPI motor_load_effect_async( IUnknown *invoker, IUnknown *para static HRESULT WINAPI motor_LoadEffectAsync( IForceFeedbackMotor *iface, IForceFeedbackEffect *effect, IAsyncOperation_ForceFeedbackLoadEffectResult **async_op ) { + struct effect *impl = impl_from_IForceFeedbackEffect( effect ); + ForceFeedbackEffectAxes supported_axes; + DWORD count = 0; + TRACE( "iface %p, effect %p, async_op %p.\n", iface, effect, async_op ); + + if (SUCCEEDED(IForceFeedbackMotor_get_SupportedAxes( iface, &supported_axes ))) + { + if (supported_axes & ForceFeedbackEffectAxes_X) + count++; + if (supported_axes & ForceFeedbackEffectAxes_Y) + { + if (!count && impl->axes[0] != DIJOFS_Y) + { + impl->directions[0] = impl->directions[1]; + impl->axes[0] = DIJOFS_Y; + } + count++; + } + if (supported_axes & ForceFeedbackEffectAxes_Z) + { + if (count < 2 && impl->axes[count] != DIJOFS_Z) + { + impl->directions[count] = impl->directions[2]; + impl->axes[count] = DIJOFS_Z; + } + count++; + } + } + impl->params.cAxes = count; + return async_operation_effect_result_create( (IUnknown *)iface, (IUnknown *)effect, motor_load_effect_async, async_op ); }
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 | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-)
diff --git a/dlls/windows.gaming.input/provider.c b/dlls/windows.gaming.input/provider.c index 39229a35c7d..d5703ec76d9 100644 --- a/dlls/windows.gaming.input/provider.c +++ b/dlls/windows.gaming.input/provider.c @@ -141,6 +141,14 @@ 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 ); @@ -153,9 +161,23 @@ static HRESULT WINAPI wine_provider_get_Type( IWineGameControllerProvider *iface
switch (GET_DIDEVICE_TYPE( instance.dwDevType )) { - case DI8DEVTYPE_DRIVING: *value = WineGameControllerType_RacingWheel; break; - case DI8DEVTYPE_GAMEPAD: *value = WineGameControllerType_Gamepad; break; - default: *value = WineGameControllerType_Joystick; break; + case DI8DEVTYPE_DRIVING: + *value = WineGameControllerType_RacingWheel; + break; + case DI8DEVTYPE_GAMEPAD: + *value = WineGameControllerType_Gamepad; + break; + default: + { + DWORD count = 0; + if (FAILED(IDirectInputDevice8_EnumObjects( impl->dinput_device, count_ffb_axes, &count, DIDFT_AXIS ))) + count = 0; + + if (count == 1) + *value = WineGameControllerType_RacingWheel; + else *value = WineGameControllerType_Joystick; + break; + } }
return S_OK;
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 | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-)
diff --git a/dlls/dinput/tests/force_feedback.c b/dlls/dinput/tests/force_feedback.c index 122d11dbcdd..2c3bd0c5708 100644 --- a/dlls/dinput/tests/force_feedback.c +++ b/dlls/dinput/tests/force_feedback.c @@ -4879,19 +4879,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), @@ -5478,7 +5477,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[] = @@ -5509,7 +5508,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[] = @@ -5547,7 +5546,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[] = @@ -5585,7 +5584,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 = @@ -5775,7 +5774,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 );
set_hid_expect( file, &expect_pause, sizeof(expect_pause) ); @@ -6080,6 +6079,12 @@ 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 );
Awesome, I was completely clueless with that FH5 issue!
I've made a few tweaks if you don't mind looking at it and updating the MR: https://gitlab.winehq.org/rbernon/wine/-/commits/mr/1535-tweaks
More specifically I added a test for the initially modified parameters fix and it looks like the envelope report is supposed to be sent too. So I changed the fix to simply consider that all effect parameters are initially modified, and send the envelope only if it was also specified.
I also then reordered the commits a bit and tweaked the axis mapping logic, so that it's done within the async operation and while holding the effect critical section to serialize concurrent accesses.
Thanks! Looks good. No problem updating it.
Is there any automated way merging your branch into the MR? Or I just merge it and force update it myself.
No you need to fetch it and force push yours. It's otherwise possible to give access to your repository to other people so they can force push to the MR branch themselves but I don't like it.