Under Windows and the PID specification it is possible to specify less than the full set of condition blocks. Under SDL and the Linux FF input API all condition blocks must always be specified.
The existing code does not set any values outside the specified axes. This effectively sets the strength to zero along the unspecified axes. Testing setting just one axis with the MS Sidewinder 2 under Windows, however, reveals that it sets the first axis (the X-axis) to the given parameter values and sets the other axis to full strength (this is most easily felt/tested with the spring effect).
This sets unspecified axes in the SDL and udev input backends to play at full strength to agree with Windows. It also updates joy.cpl to play condition effects along just the indicated axis too (as is done for non-condition effects).
-- v2: joycpl: Play condition effects on indicated axis too
From: Tyson Whitehead twhitehead@gmail.com
Under Windows and the PID specification it is possible to specify less than the full set of condition blocks. Under SDL and the Linux FF input API all condition blocks must always be specified.
What values should then be used for any unspecified axes under these? The exsting code implicitly sets playback strength to zero for unspesified axes. Testing a MS Sidewinder 2 under Windows and hidraw, however, indicates this is incorrect. Unspecified axes actually playback at full strength, not no strength.
This sets unspecified axes in the SDL and input backends to play at full strength to agree with Windows and hidraw. --- dlls/winebus.sys/bus_sdl.c | 29 +++++++++++++++-------------- dlls/winebus.sys/bus_udev.c | 28 ++++++++++++++-------------- 2 files changed, 29 insertions(+), 28 deletions(-)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index 852541bf4f0..3a12183534a 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -652,6 +652,7 @@ static NTSTATUS sdl_device_physical_effect_update(struct unix_device *iface, BYT struct sdl_device *impl = impl_from_unix_device(iface); int id = impl->effect_ids[index]; SDL_HapticEffect effect = {0}; + int i; INT32 direction; NTSTATUS status;
@@ -699,23 +700,23 @@ static NTSTATUS sdl_device_physical_effect_update(struct unix_device *iface, BYT effect.condition.direction.type = SDL_HAPTIC_SPHERICAL; effect.condition.direction.dir[0] = direction; effect.condition.direction.dir[1] = params->direction[1]; - if (params->condition_count >= 1) + + for (i = 0; i < max(params->condition_count, 3); i++) { - effect.condition.right_sat[0] = params->condition[0].positive_saturation; - effect.condition.left_sat[0] = params->condition[0].negative_saturation; - effect.condition.right_coeff[0] = params->condition[0].positive_coefficient; - effect.condition.left_coeff[0] = params->condition[0].negative_coefficient; - effect.condition.deadband[0] = params->condition[0].dead_band; - effect.condition.center[0] = params->condition[0].center_point_offset; + effect.condition.right_sat[i] = params->condition[i].positive_saturation; + effect.condition.left_sat[i] = params->condition[i].negative_saturation; + effect.condition.right_coeff[i] = params->condition[i].positive_coefficient; + effect.condition.left_coeff[i] = params->condition[i].negative_coefficient; + effect.condition.deadband[i] = params->condition[i].dead_band; + effect.condition.center[i] = params->condition[i].center_point_offset; } - if (params->condition_count >= 2) + /* Testing MS Sidewinder 2 indicates unspecified paramater blocks are full strength */ + for (; i < 3; i++) { - effect.condition.right_sat[1] = params->condition[1].positive_saturation; - effect.condition.left_sat[1] = params->condition[1].negative_saturation; - effect.condition.right_coeff[1] = params->condition[1].positive_coefficient; - effect.condition.left_coeff[1] = params->condition[1].negative_coefficient; - effect.condition.deadband[1] = params->condition[1].dead_band; - effect.condition.center[1] = params->condition[1].center_point_offset; + effect.condition.right_sat[i] = 65535; + effect.condition.left_sat[i] = 65535; + effect.condition.right_coeff[i] = 32767; + effect.condition.left_coeff[i] = 32767; } break;
diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index e7b936198eb..5c108f59c4c 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -998,6 +998,7 @@ static NTSTATUS lnxev_device_physical_effect_update(struct unix_device *iface, B { struct lnxev_device *impl = lnxev_impl_from_unix_device(iface); struct ff_effect effect = {.id = impl->effect_ids[index]}; + int i; NTSTATUS status;
TRACE("iface %p, index %u, params %p.\n", iface, index, params); @@ -1035,23 +1036,22 @@ static NTSTATUS lnxev_device_physical_effect_update(struct unix_device *iface, B case PID_USAGE_ET_DAMPER: case PID_USAGE_ET_INERTIA: case PID_USAGE_ET_FRICTION: - if (params->condition_count >= 1) + for (i = 0; i < max(params->condition_count, 2); i++) { - effect.u.condition[0].right_saturation = params->condition[0].positive_saturation; - effect.u.condition[0].left_saturation = params->condition[0].negative_saturation; - effect.u.condition[0].right_coeff = params->condition[0].positive_coefficient; - effect.u.condition[0].left_coeff = params->condition[0].negative_coefficient; - effect.u.condition[0].deadband = params->condition[0].dead_band; - effect.u.condition[0].center = params->condition[0].center_point_offset; + effect.u.condition[i].right_saturation = params->condition[i].positive_saturation; + effect.u.condition[i].left_saturation = params->condition[i].negative_saturation; + effect.u.condition[i].right_coeff = params->condition[i].positive_coefficient; + effect.u.condition[i].left_coeff = params->condition[i].negative_coefficient; + effect.u.condition[i].deadband = params->condition[i].dead_band; + effect.u.condition[i].center = params->condition[i].center_point_offset; } - if (params->condition_count >= 2) + /* Testing MS Sidewinder 2 indicates unspecified paramater blocks are full strength */ + for (; i < 2; i++) { - effect.u.condition[1].right_saturation = params->condition[1].positive_saturation; - effect.u.condition[1].left_saturation = params->condition[1].negative_saturation; - effect.u.condition[1].right_coeff = params->condition[1].positive_coefficient; - effect.u.condition[1].left_coeff = params->condition[1].negative_coefficient; - effect.u.condition[1].deadband = params->condition[1].dead_band; - effect.u.condition[1].center = params->condition[1].center_point_offset; + effect.u.condition[i].right_saturation = 65535; + effect.u.condition[i].left_saturation = 65535; + effect.u.condition[i].right_coeff = 32767; + effect.u.condition[i].left_coeff = 32767; } break;
From: Tyson Whitehead twhitehead@gmail.com
Testing with a MS Sidewinde 2 under Windows reveals that playing a codition effect on just one axis can only be done by explicitly setting the other axes to a zero strength as unspecified axes run at full strength and not no strength as might be expected.
The choosen axis must also be the Y axis in order for the result of the rotation to agree with what happens with non-condition effects. This is because the the -Y axis is the reference axis for the direction (i.e., no rotation plays back on the Y axis). --- dlls/joy.cpl/dinput.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-)
diff --git a/dlls/joy.cpl/dinput.c b/dlls/joy.cpl/dinput.c index 925c6d0038c..890056cb1f4 100644 --- a/dlls/joy.cpl/dinput.c +++ b/dlls/joy.cpl/dinput.c @@ -92,12 +92,17 @@ static BOOL CALLBACK enum_effects( const DIEFFECTINFOW *info, void *context ) .dwMagnitude = DI_FFNOMINALMAX, .dwPeriod = DI_SECONDS / 2, }; - DICONDITION condition = + DICONDITION condition[2] = { - .dwPositiveSaturation = 10000, - .dwNegativeSaturation = 10000, - .lPositiveCoefficient = 10000, - .lNegativeCoefficient = 10000, + /* Playback on y-axis only so rotates to correct position */ + { + }, + { + .dwPositiveSaturation = 10000, + .dwNegativeSaturation = 10000, + .lPositiveCoefficient = 10000, + .lNegativeCoefficient = 10000, + }, }; DIRAMPFORCE ramp = { @@ -130,8 +135,16 @@ static BOOL CALLBACK enum_effects( const DIEFFECTINFOW *info, void *context ) break; }
- do hr = IDirectInputDevice2_CreateEffect( device, &info->guid, ¶ms, &effect, NULL ); - while (FAILED(hr) && --params.cAxes); + if ( FAILED(hr = IDirectInputDevice2_CreateEffect( device, &info->guid, ¶ms, &effect, NULL )) ) + { + params.cAxes = 1; + if ( DIEFT_GETTYPE( info->dwEffType ) == DIEFT_CONDITION ) + { + params.cbTypeSpecificParams = sizeof(condition[1]); + params.lpvTypeSpecificParams = &condition[1]; + } + hr = IDirectInputDevice2_CreateEffect( device, &info->guid, ¶ms, &effect, NULL ); + }
if (FAILED(hr)) {
I wonder if this is the same kind of thing as for autocenter, where SDL/evdev simply lack the ability to indicate how many conditions blocks are provided and where explicitly setting them to full force will force device drivers to all operate in the same manner, when they could decide to differ and have device-specific behavior (for instance as on Windows) if they were better informed.
Although in this case and unlike autocenter where we have three possibilities (unset/on/off), the current and default is to have all extra conditions implicitly set to 0. So I guess here it's just a decision whether we set it to 0 or full force.
CC @TomaszPakula may I ask your opinion here as you seem to be directly involved in kernel-side evdev development?
On Tue Aug 26 09:59:11 2025 +0000, Rémi Bernon wrote:
I wonder if this is the same kind of thing as for autocenter, where SDL/evdev simply lack the ability to indicate how many conditions blocks are provided and where explicitly setting them to full force will force device drivers to all operate in the same manner, when they could decide to differ and have device-specific behavior (for instance as on Windows) if they were better informed. Although in this case and unlike autocenter where we have three possibilities (unset/on/off), the current and default is to have all extra conditions implicitly set to 0. So I guess here it's just a decision whether we set it to 0 or full force. CC @TomaszPakula may I ask your opinion here as you seem to be directly involved in kernel-side evdev development?
Linux exclusively uses polar direction and with polar, PID says that effects use n (n is the number of axes) condition blocks to define forces BUT at least two.
For now, Linux expects exactly two condition blocks initialised and the API doesn't really specifies how they will be used. It's impossible for now to define more blocks (for example, for a 4-axis effect) as it's even impossisble to define number of axes and the axes themselves.
The standard here is a little confusing since at least two blocks are supposed to be sent but
If the number of Condition report blocks is equal to the number of axes for the effect, then the first report
block applies to the first axis, the second applies to the second axis, and so on. For example, a two-axis spring condition with CP Offset set to zero in both Condition report blocks would have the same effect as the joystick self-centering spring. When a condition is defined for each axis in this way, the effect must not be rotated. If there is a single Condition report block for an effect with more than one axis, then the direction along which the parameters of the Condition report block are in effect is determined by the direction parameters passed in the Direction field of the Effect report block. For example, a friction condition rotated 45 degrees (in polar coordinates) would resist joystick motion in the northeast-southwest direction but would have no effect on joystick motion in the northwest-southeast direction.
With this, the end effect would be determined by the effect direction fields or the number of condition parameters send should dictate of the condition fileds are even considered?
I must say, it's a bit unclear and this is where having a device to literally test out how it feels would be best. I think we should rely on @twhitehead testing here. At least for one-axis effects like in racing, this has no effect so either 0 or full is fine. Ideally, we'd want to compare with a different PID enabled joystick but with the current FF api in Linux, it wouldn't even be possible to determine if we could not send the second condition block.
On Tue Aug 26 10:11:39 2025 +0000, Tomasz Pakuła wrote:
Linux exclusively uses polar direction and with polar, PID says that effects use n (n is the number of axes) condition blocks to define forces BUT at least two. For now, Linux expects exactly two condition blocks initialised and the API doesn't really specifies how they will be used. It's impossible for now to define more blocks (for example, for a 4-axis effect) as it's even impossisble to define number of axes and the axes themselves. The standard here is a little confusing since at least two blocks are supposed to be sent but
If the number of Condition report blocks is equal to the number of
axes for the effect, then the first report block applies to the first axis, the second applies to the second axis, and so on. For example, a two-axis spring condition with CP Offset set to zero in both Condition report blocks would have the same effect as the joystick self-centering spring. When a condition is defined for each axis in this way, the effect must not be rotated.
If there is a single Condition report block for an effect with more
than one axis, then the direction along which the parameters of the Condition report block are in effect is determined by the direction parameters passed in the Direction field of the Effect report block. For example, a friction condition rotated 45 degrees (in polar coordinates) would resist joystick motion in the northeast-southwest direction but would have no effect on joystick motion in the northwest-southeast direction. With this, the end effect would be determined by the effect direction fields or the number of condition parameters send should dictate of the condition fileds are even considered? At le the least, a on-axis effect should only consider one condition block. I must say, it's a bit unclear and this is where having a device to literally test out how it feels would be best. I think we should rely on @twhitehead testing here. At least for one-axis effects like in racing, this has no effect so either 0 or full is fine. Ideally, we'd want to compare with a different PID enabled joystick but with the current FF api in Linux, it wouldn't even be possible to determine if we could not send the second condition block.
I did dump and decode the HID packets on Windows too when testing this. As with the hidraw backend, Windows just directly passes a single axis condition block along to the device as specified, so it is entirely device dependent.
Wish I had more devices to test things with, but all I have is the MS Sidewinder 2.
I understand the MS sidewinder FF series where the first mass marketed FF sticks, so I guess there is some hope the other vendors followed suite on how thing work on the sidewinders.
This merge request was closed by Rémi Bernon.