FFB Autocenter introduced in https://gitlab.winehq.org/wine/wine/-/merge_requests/4911 had one major misunderstanding.
The USB PID standard doesn't actually define any explicit way to autocenter a device. One could of course use the spring effect with a deadzone of 0 and dead band of 0. This is what I'm actually working on for the Linux PID driver (spring + friction/damper).
Some devices implement autocenter in firmware when they receive the DC Disable Actuators command. Very few, if not just one, implement this weird autocenter effect on slot 1. This is, from what I can gather, only implemented on the MS SideWinder joystick(s) and the Windows' USB PID driver is created around these devices.
Windows PID driver is a bit out of spec, is quite permissive when it comes to fields missing in the descriptor (basically, only effect types and their effect type blocks are optional). Another thing it does is handling of this out-of-spec autocentering for their joysticks. Funnliy enough, the creator of the Linux PID driver based the initial code on testing with MS Sidewinder so it's autocentering is supported.
This is where the autocentering mentioned in the MR comes from. It's not the directinput api that does it but the Windows PID driver. As such, autocentering on reset should be left to the drivers, not handeled by Wine.
SDL lacks full reset support and Linux is even more barebones, whre it's not even possible to query the device state, effects etc (something I'm working on slowly). As such, when games send out RESET to prepare the device, the device starts autocentering for no good reason and the effect is not removed once other effect are uploaded and played which would be the case for MS sidewinder.
In any case, even Windows in inconsistent and I think the directinput documentation has some errors. directinput ffb api is largely based on USB PID but:


Testing with my Moza Racing R9 wheelbase, the DC Reset PID command is sent, but there's no DC Disable Actuators in sight. The games still call reset then enable actuators though.
tl;dr Set autocentering to 0 instead of max value when DISFFC_RESET is received to remove the unwanted autocenter behavior.
Signed-off-by: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
-- v3: winebus: Do not touch autocenter on device init and device reset
From: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com
FFB Autocenter introduced in https://gitlab.winehq.org/wine/wine/-/merge_requests/4911 had one major misunderstanding.
The USB PID standard doesn't actually define any explicit way to autocenter a device. One could of course use the spring effect with a deadzone of 0 and dead band of 0. This is what I'm actually working on for the Linux PID driver (spring + friction/damper).
Some devices implement autocenter in firmware when they receive the DC Disable Actuators command. Very few, if not just one, implement this weird autocenter effect on slot 1. This is, from what I can gather, only implemented on the MS SideWinder joystick(s) and the Windows' USB PID driver is created around these devices.
Windows PID driver is a bit out of spec, is quite permissive when it comes to fields missing in the descriptor (basically, only effect types and their effect type blocks are optional). Another thing it does is handling of this out-of-spec autocentering for their joysticks. Funnliy enough, the creator of the Linux PID driver based the initial code on testing with MS Sidewinder so it's autocentering is supported.
This is where the autocentering mentioned in the MR comes from. It's not the directinput api that does it but the Windows PID driver. As such, autocentering on reset should be left to the drivers, not handeled by Wine.
SDL lacks full reset support and Linux is even more barebones, whre it's not even possible to query the device state, effects etc (something I'm working on slowly). As such, when games send out RESET to prepare the device, the device starts autocentering for no good reason and the effect is not removed once other effect are uploaded and played which would be the case for MS sidewinder.
tl;dr Set autocentering to 0 instead of max value when DISFFC_RESET is reveived to remove the unwanted autocenter behavior.
Signed-off-by: Tomasz Pakuła tomasz.pakula.oficjalny@gmail.com --- dlls/dinput/device.c | 1 - dlls/winebus.sys/bus_sdl.c | 1 - dlls/winebus.sys/bus_udev.c | 1 - 3 files changed, 3 deletions(-)
diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c index 31240d03f65..d6cb55cbfe1 100644 --- a/dlls/dinput/device.c +++ b/dlls/dinput/device.c @@ -2153,7 +2153,6 @@ void dinput_device_init( struct dinput_device *device, const struct dinput_devic device->caps.dwSize = sizeof(DIDEVCAPS); device->caps.dwFlags = DIDC_ATTACHED | DIDC_EMULATED; device->device_gain = 10000; - device->autocenter = DIPROPAUTOCENTER_ON; device->force_feedback_state = DIGFFS_STOPPED | DIGFFS_EMPTY; InitializeCriticalSectionEx( &device->crit, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO ); dinput_internal_addref( (device->dinput = dinput) ); diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index 5cec049a845..bb074604e75 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -560,7 +560,6 @@ static NTSTATUS sdl_device_physical_device_control(struct unix_device *iface, US pSDL_HapticDestroyEffect(impl->sdl_haptic, impl->effect_ids[i]); impl->effect_ids[i] = -1; } - pSDL_HapticSetAutocenter(impl->sdl_haptic, 100); return STATUS_SUCCESS; case PID_USAGE_DC_DEVICE_PAUSE: pSDL_HapticPause(impl->sdl_haptic); diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index 39b810b7588..cc8a0889c76 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -921,7 +921,6 @@ static NTSTATUS lnxev_device_physical_device_control(struct unix_device *iface, WARN("couldn't free effect, EVIOCRMFF ioctl failed: %d %s\n", errno, strerror(errno)); impl->effect_ids[i] = -1; } - lnxev_device_physical_device_set_autocenter(iface, 100); return STATUS_SUCCESS; case PID_USAGE_DC_DEVICE_PAUSE: WARN("device pause not supported\n");
I don't know what do do about the tests though as they are weird. The USB PID standard does NOT support autocenter so the only thing they are testing is this one specific out-of-spec thing only for Side Winder joysticks. This *maybe* could be useful if Wine wants to specifically support SideWinder in HIDRAW mode but then it should check device VID:PID and change how it works internally. Not in the bus but in the PID implemenmtation
The problem I see is that we still touch autocenter on `PID_USAGE_DC_STOP_ALL_EFFECTS`, and we will never request anything different after that.
Instead, and in addition to allowing user to configure autocenter as they like, the Proton patches are also changing the default behavior by reading the initial device `/sys/class/*/autocenter` value and use it when resetting device. Wouldn't that help for this case?
I can even tweak it and disable autocenter requests entirely (in the SDL/evdev backends) if that property is absent, I have no idea whether it is a common property or not. Something like https://gitlab.winehq.org/wine/wine/-/merge_requests/8686 for instance.
On Thu Jul 31 15:43:54 2025 +0000, Tomasz Pakuła wrote:
I don't know what do do about the tests though as they are weird. The USB PID standard does NOT support autocenter so the only thing they are testing is this one specific out-of-spec thing only for Side Winder joysticks. This *maybe* could be useful if Wine wants to specifically support SideWinder in HIDRAW mode but then it should check device VID:PID and change how it works internally. Not in the bus but in the PID implemenmtation
The tests check how Windows DInput implementation behaves in the presence of a HID PID-compatible HID device, they are not specific to Microsoft devices as they use a different VID/PID.
We use the PID protocol internally to communicate between DInput and WineBus, because it's convenient and because it allows us to pass through everything in the hidraw case (which IMO is the best case scenario, and SDL/evdev backend will suffer from impedance mismatch as shown here).
I would even think that protocol conversion should probably be done in the Linux device drivers from HID PID (exposed to user space and Wine through "hidraw") to whatever vendor protocol the devices are using, instead of the current Wine HID PID (over SDL) over evdev over vendor, which suffers from multiple mismatching abstractions.
The thing is, `/sys/class/*/autocenter` is a non-standard property only exposed by some out-of-tree drivers. What I intend to do is to actually add device-querying capabilities to the Linux FFB api so these things could be easily obtained.
autocenter and other sysfs knobs were exposed basically for use by `Oversteer`
Moreover, if users actually want to control the autocenter behavior, they could already do it outside of wine wine software/commands specific to their hardware.
On Thu Jul 31 15:44:13 2025 +0000, Rémi Bernon wrote:
The tests check how Windows DInput implementation behaves in the presence of a HID PID-compatible HID device, they are not specific to Microsoft devices as they use a different VID/PID. We use the HID PID protocol internally to communicate between DInput and WineBus, because it's convenient and because it allows us to pass through everything in the hidraw case (which IMO is the best case scenario, and SDL/evdev backend will suffer from impedance mismatch as shown here). I would even think that protocol conversion should probably be done in the Linux device drivers from HID PID (exposed to user space and Wine through "hidraw") to whatever vendor protocol the devices are using, instead of the current Wine HID PID (over SDL) over evdev over vendor, which suffers from multiple mismatching abstractions.
Okay, so without doing this out-of-spec thing, it would be impossible to pass along autocenter at all right? Or is it still impossible at this moment?