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
-- v4: 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 | 2 -- dlls/winebus.sys/bus_udev.c | 2 -- 3 files changed, 5 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..665e4d2eeb3 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -550,7 +550,6 @@ static NTSTATUS sdl_device_physical_device_control(struct unix_device *iface, US return STATUS_SUCCESS; case PID_USAGE_DC_STOP_ALL_EFFECTS: pSDL_HapticStopAll(impl->sdl_haptic); - pSDL_HapticSetAutocenter(impl->sdl_haptic, 0); return STATUS_SUCCESS; case PID_USAGE_DC_DEVICE_RESET: pSDL_HapticStopAll(impl->sdl_haptic); @@ -560,7 +559,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..f3b210890b0 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -911,7 +911,6 @@ static NTSTATUS lnxev_device_physical_device_control(struct unix_device *iface, if (impl->effect_ids[i] < 0) continue; lnxev_device_physical_effect_run(impl, i, 0); } - lnxev_device_physical_device_set_autocenter(iface, 0); return STATUS_SUCCESS; case PID_USAGE_DC_DEVICE_RESET: for (i = 0; i < ARRAY_SIZE(impl->effect_ids); ++i) @@ -921,7 +920,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 think the main issue is that just watching the USB traffic from windows is not enough to understand what's being done by the Dinput and what's just done by the driver AND when a new device is plugged in on Windows, it can do whatever it wants to do, and what the driver need to do to prepare it. Win Wine, the device is already prepared by the system and drivers, so some things should simply not be replicated. The autocentering is one of the better examples.
On Thu Jul 31 16:30:39 2025 +0000, Tomasz Pakuła wrote:
I think the main issue is that just watching the USB traffic from windows is not enough to understand what's being done by the Dinput and what's just done by the driver AND when a new device is plugged in on Windows, it can do whatever it wants to do, and what the driver need to do to prepare it. Win Wine, the device is already prepared by the system and drivers, so some things should simply not be replicated. The autocentering is one of the better examples.
The tests use a virtual HID device and capture/compare what DInput requests to the device driver directly on the HID level.
It has then also been manually compared against USB traffic by other people, and with HID PID-capable non-Microsoft devices like Simucube.
On Thu Jul 31 16:30:39 2025 +0000, Rémi Bernon wrote:
The tests use a virtual HID device and capture/compare what DInput requests to the device driver directly on the HID level. It has then also been manually compared against USB traffic by other people, and with HID PID-capable non-Microsoft devices like Simucube.
Sure, but was the autocenter confirmed on anything other than SideWinder joysticks? Becasue on my HID PID compatible Moza R9, the stuff for autocentering is never sent out. I presume the windows driver handles this similarly to `hid-pidff` and actually tests if the device supports that non-standard autocentering first, and only reports autocenter supported if that's the case.
https://codebrowser.dev/linux/linux/drivers/hid/usbhid/hid-pidff.c.html#pidf...
I'd have to check with SDL, but at least on Linux, the autocenter support is reported with `FF_AUTOCENTER` bit on the ffbit
I think I'm unclear on what "user release the device" means in `DIPROPAUTOCENTER_ON: The device should automatically center when the user releases the device.`
On Thu Jul 31 16:00:15 2025 +0000, Tomasz Pakuła wrote:
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?
We implement the HID PID protocol the way Microsoft does it in dinput.dll, and it has some autocenter semantics we cannot really change without breaking hidraw support (ie: autocenter should be enabled after PID_USAGE_DC_DEVICE_RESET is received on the device side, disabled after PID_USAGE_DC_STOP_ALL_EFFECTS is received. DInput will send PID_USAGE_DC_STOP_ALL_EFFECTS right after PID_USAGE_DC_DEVICE_RESET if autocenter has been disabled on the device by the application).
We can possibly consider adding some Wine-specific bits in the report descriptors, implemented on both dinput and winebus sides to better support SDL/evdev cases, but neither SDL nor evdev seem to have detailed enough API to control these things, and it doesn't solve any vendor-specific behavior problem we may have.
Many vendors don't use the generic HID PID DInput driver on Windows but ship their own DInput and NT device driver. Applications may embed specific knowledge of these devices, and of their Windows drivers behavior. For these, either we would either need to rewrite each vendor driver, which is probably not something we want to do, or we need to work to make the Windows driver work in Wine.
On Thu Jul 31 16:42:49 2025 +0000, Rémi Bernon wrote:
We implement the HID PID protocol the way Microsoft does it in dinput.dll, and it has some autocenter semantics we cannot really change without breaking hidraw support (ie: autocenter should be enabled after PID_USAGE_DC_DEVICE_RESET is received on the device side, disabled after PID_USAGE_DC_STOP_ALL_EFFECTS is received. DInput will send PID_USAGE_DC_STOP_ALL_EFFECTS right after PID_USAGE_DC_DEVICE_RESET if autocenter has been disabled on the device by the application). We can possibly consider adding some Wine-specific bits in the report descriptors, implemented on both dinput and winebus sides to better support SDL/evdev cases, but neither SDL nor evdev seem to have detailed enough API to control these things, and it doesn't solve any vendor-specific behavior problem we may have. Many vendors don't use the generic HID PID DInput driver on Windows but ship their own DInput and NT device driver. Applications may embed specific knowledge of these devices, and of their Windows drivers behavior. For these, either we would either need to rewrite each vendor driver, which is probably not something we want to do, or we need to work to make the Windows driver work in Wine.
Okay, yeah. I think I finally understand the issue at hand here. The autocenter behavior can/is different for other drivers, but since everything in Wine passes through PID, that's they way everything will behave on the other side.
Since no FFB racing wheel that's build upon PID supports this non-standard auto-centering, some games might not actually disable autocenter properly because it didn't come up in their testing.
In my testing, I found that `PID_USAGE_DC_STOP_ALL_EFFECTS` does not actually always come after reset, at least in ETS2/ATS but maybe this game actually check first if the device supports autocenter, instead of just blindly disabling it?
was the autocenter confirmed on anything other than SideWinder joysticks
Yep
Logitech 'old' implementation: https://elixir.bootlin.com/linux/v6.16/source/drivers/hid/hid-lg4ff.c#L90
`new-lg4ff` implementation: https://github.com/berarma/new-lg4ff/blob/master/hid-lg4ff.c#L178
Logitech 'old' protocol: https://opensource.logitech.com/wiki/force_feedback/Logitech_Force_Feedback_..., page 15
Logitech implementation within 'hidpp' protocol: https://elixir.bootlin.com/linux/v6.16/source/drivers/hid/hid-logitech-hidpp...
iforce: https://elixir.bootlin.com/linux/v6.16/source/drivers/input/joystick/iforce/...
And that's it regarding devices 'in current kernel', excluding `hid-pidff` ones. Thrustmaster out-of-tree driver tmff2 has autocenter bit set as well (https://github.com/Kimplul/hid-tmff2/blob/e0a173f3265be8efcbd4a6f34df6c5713d...), and i think that on Windows autocentering property is set to `true` also.
On Thu Jul 31 16:52:57 2025 +0000, Oleg Makarenko wrote:
was the autocenter confirmed on anything other than SideWinder joysticks
Yep Logitech 'old' implementation: https://elixir.bootlin.com/linux/v6.16/source/drivers/hid/hid-lg4ff.c#L90 `new-lg4ff` implementation: https://github.com/berarma/new-lg4ff/blob/master/hid-lg4ff.c#L178 Logitech 'old' protocol: https://opensource.logitech.com/wiki/force_feedback/Logitech_Force_Feedback_..., page 15 Logitech implementation within 'hidpp' protocol: https://elixir.bootlin.com/linux/v6.16/source/drivers/hid/hid-logitech-hidpp... iforce: https://elixir.bootlin.com/linux/v6.16/source/drivers/input/joystick/iforce/... And that's it regarding devices 'in current kernel', excluding `hid-pidff` ones. Thrustmaster out-of-tree driver tmff2 has autocenter bit set as well (https://github.com/Kimplul/hid-tmff2/blob/e0a173f3265be8efcbd4a6f34df6c5713d...), and i think that on Windows autocentering property is set to `true` also.
I meant specifically PID devices
On Thu Jul 31 16:50:21 2025 +0000, Tomasz Pakuła wrote:
Okay, yeah. I think I finally understand the issue at hand here. The autocenter behavior can/is different for other drivers, but since everything in Wine passes through PID, that's they way everything will behave on the other side. Since no FFB racing wheel that's build upon PID supports this non-standard auto-centering, some games might not actually disable autocenter properly because it didn't come up in their testing. In my testing, I found that `PID_USAGE_DC_STOP_ALL_EFFECTS` does not actually always come after reset, at least in ETS2/ATS but maybe this game actually check first if the device supports autocenter, instead of just blindly disabling it?
What is being tested is that:
``` IDirectInputDevice8_SetProperty( device, DIPROP_AUTOCENTER, DIPROPAUTOCENTER_OFF ) IDirectInputDevice8_Acquire( device ) ```
Triggers `PID_USAGE_DC_DEVICE_RESET` + `PID_USAGE_DC_STOP_ALL_EFFECTS`, and that:
``` IDirectInputDevice8_SetProperty( device, DIPROP_AUTOCENTER, DIPROPAUTOCENTER_ON ) IDirectInputDevice8_Acquire( device ) ```
Triggers `PID_USAGE_DC_DEVICE_RESET` only, and by default the device has the property set to `DIPROPAUTOCENTER_ON` (so `Acquire` will trigger the latter).
And that seems to be the only effect of DIPROP_AUTOCENTER with the generic HID PID DInput driver. Of course, applications may also have different expectations for the reasons above if a vendor-specific DInput driver should be involved.
On Thu Jul 31 16:55:19 2025 +0000, Rémi Bernon wrote:
What is being tested is that:
IDirectInputDevice8_SetProperty( device, DIPROP_AUTOCENTER, DIPROPAUTOCENTER_OFF ) IDirectInputDevice8_Acquire( device )
Triggers `PID_USAGE_DC_DEVICE_RESET` + `PID_USAGE_DC_STOP_ALL_EFFECTS`, and that:
IDirectInputDevice8_SetProperty( device, DIPROP_AUTOCENTER, DIPROPAUTOCENTER_ON ) IDirectInputDevice8_Acquire( device )
Triggers `PID_USAGE_DC_DEVICE_RESET` only, and by default the device has the property set to `DIPROPAUTOCENTER_ON` (so `Acquire` will trigger the latter). And that seems to be the only effect of DIPROP_AUTOCENTER with the generic HID PID DInput driver. Of course, applications may also have different expectations for the reasons above if a vendor-specific DInput driver should be involved.
Hmm, so I wonder, why is there an issue now that in some games, the autocenter is turned on and never actually goes away though it works properly in windows, where the autocenter is disabled,
On Thu Jul 31 17:00:26 2025 +0000, Tomasz Pakuła wrote:
Hmm, so I wonder, why is there an issue now that in some games, the autocenter is turned on and never actually goes away though it works properly in windows, where the autocenter is disabled,
Well, I guess just not touching autocenter on evdev and sdl would be enough for now. Autocenter would still work with hidraw. At least with evdev, I have hopes that proper deice control will be in this year