This merge request restores `DIPROP_AUTOCENTER` support to the SDL and udev input backends. It also * adds an `Autocenter` registry app key that lets the default `DIPROP_AUTOCENTER` setting to be set on a per device and per application and device level, * logs a warning on acquisition when `DIPROP_AUTOCENTER` hasn't been checked or set that that is the case and the default can be changed via the 'Autocenter' app key if required.
Together these two should provide a robust solution for the issues some where having with autocenter being incorrectly on for some programs and devices.
-- v4: joy.cpl: Add button to set autocenter initial value registry key
From: Tyson Whitehead twhitehead@gmail.com
The removal of autocenter support from the SDL and udev input backends was based on the belief that the device should not turn auto centering on upon receiving a device reset command from the front end as only some devices do it this way and others have different ways of doing it.
This is a fundamental misunderstanding of what is going on. The messages between the front end and the SDL and udev backends are the front end telling the backends what to do. These are in no way sent to the device, and have nothing to do with how autocenter is turned on or off on the device. Rather the backends interpret them and then makes SDL and udev input API calls to drive the device, including turning on or off autocentering.
The reason the backends call their respective APIs to turn on autocentering on receiving a reset is because that is they way turn on autocenter is communicated to the backend. In the event that the autocenter should not be on, the front end will follow the reset with a stop all effects command which tells the backends to call their underlying API to turn it back off.
If autocenter is different than a users experts it to be, either * the program assumed a given initial state, didn't verify it or or set it, and the initial state was the other, or * the backend API calls are failing to set it (the kernel driver either does not support setting it or is buggy).
I have written a test program that lets people test their devices to ensure things are working correctly and discover what the initial state is under Windows
https://github.com/twhitehead/issue-wine-ff-autocenter
A log file is generated for users to submit so initial states under Windows can be discovered and broken drivers identified. This will allow the unwanted autocenter issue of the author of the commit being reverted to be properly figured out and addressed.
As a final note, the SDL and udev input backends are actually the preferred backends for force feedback as they ultimately dispatch to the kernel driver which can handle any required quirks as well as all the different way autocentering can be implemented.
By comparison, the udev hidraw backend is quite limited. It requires a PID compliant device without any quirks that uses reset and stop all effect autocentering. Such devices are perfectly supported by the kernel ff_pdiff driver, so they to function perfectly with the SDL and udev input backends.
This reverts commit b0db6d50530023a19846f08f1c62f14803448f59. --- dlls/winebus.sys/bus_sdl.c | 2 ++ dlls/winebus.sys/bus_udev.c | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index 6e2c8bef3e4..7c622155fd8 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -518,6 +518,7 @@ 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); @@ -527,6 +528,7 @@ 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 9951e3d0ec8..c6b71492f9e 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -827,6 +827,24 @@ static NTSTATUS lnxev_device_physical_effect_run(struct lnxev_device *impl, BYTE return STATUS_SUCCESS; }
+static NTSTATUS lnxev_device_physical_device_set_autocenter(struct unix_device *iface, BYTE percent) +{ + struct lnxev_device *impl = lnxev_impl_from_unix_device(iface); + struct input_event ie = + { + .type = EV_FF, + .code = FF_AUTOCENTER, + .value = 0xffff * percent / 100, + }; + + TRACE("iface %p, percent %#x.\n", iface, percent); + + if (write(impl->base.device_fd, &ie, sizeof(ie)) == -1) + WARN("write failed %d %s\n", errno, strerror(errno)); + + return STATUS_SUCCESS; +} + static NTSTATUS lnxev_device_physical_device_control(struct unix_device *iface, USAGE control) { struct lnxev_device *impl = lnxev_impl_from_unix_device(iface); @@ -870,6 +888,7 @@ 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) @@ -879,6 +898,7 @@ 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");
From: Tyson Whitehead twhitehead@gmail.com
Programs should check and/or set DIPROP_AUTOCENTER to ensure it is the value they want. Likely some do not though if the developers never tested on devices where the Windows' default wasn't correct for them.
These programs will be broken under Wine if the Wine default is different than the Windows. This commit adds an 'Autocenter' appkey (to the existing 'Joysticks' and 'MouseWarpOverride' keys) so users can override the DIPROP_AUTOCENTER default in this case.
The Wine default is currently always DIPROPAUTOCENTER_ON, which is the correct Windows default for the MS Sidewinder 2. I don't know any other correct defaults as this is the only FF device I have to test. I started a project to collect defaults (and verify operations) for other devices though
https://github.com/twhitehead/issue-wine-ff-autocenter
so hopefully a good heuristic and a more comprehensive set of defaults can be implemented at some point. --- dlls/dinput/device.c | 56 ++++++++++++++++++++++++++++++++++++-- dlls/dinput/joystick_hid.c | 3 +- 2 files changed, 56 insertions(+), 3 deletions(-)
diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c index 31240d03f65..2aa50a844f1 100644 --- a/dlls/dinput/device.c +++ b/dlls/dinput/device.c @@ -145,6 +145,59 @@ DWORD get_config_key( HKEY defkey, HKEY appkey, const WCHAR *name, WCHAR *buffer return ERROR_FILE_NOT_FOUND; }
+static BOOL device_instance_autocenter_initial( DIDEVICEINSTANCEW *instance ) +{ + static const WCHAR on_str[] = {'o', 'n', 0}; + static const WCHAR off_str[] = {'o', 'f', 'f', 0}; + static const WCHAR joystick_key[] = {'A', 'u', 't', 'o', 'c', 'e', 'n', 't', 'e', 'r', 0}; + WCHAR buffer[MAX_PATH]; + HKEY hkey, appkey, temp; + BOOL autocenter = DIPROPAUTOCENTER_ON; /* MS Sidewinder 2 initial value. No others known yet. There is + a project to collect other device defaults to improved this + https://github.com/twhitehead/issue-wine-ff-autocenter */ + + get_app_key( &hkey, &appkey ); + + /* Autocenter settings are in the 'Autocenter' subkey */ + if (appkey) + { + if (RegOpenKeyW( appkey, joystick_key, &temp )) temp = 0; + RegCloseKey( appkey ); + appkey = temp; + } + + if (hkey) + { + if (RegOpenKeyW( hkey, joystick_key, &temp )) temp = 0; + RegCloseKey( hkey ); + hkey = temp; + } + + /* Look for the "controllername"="on"/"off" key */ + if (!get_config_key( hkey, appkey, instance->tszInstanceName, buffer, sizeof(buffer) )) + { + if (!wcscmp( on_str, buffer )) + { + TRACE( "Joystick '%s' autocenter on based on registry key.\n", debugstr_w(instance->tszInstanceName) ); + autocenter = DIPROPAUTOCENTER_ON; + } + else if (!wcscmp( off_str, buffer )) + { + TRACE( "Joystick '%s' autocenter off based on registry key.\n", debugstr_w(instance->tszInstanceName) ); + autocenter = DIPROPAUTOCENTER_OFF; + } + else + { + WARN( "Joystick '%s' autocenter registry key invalid (can only be 'on' or 'off').\n", debugstr_w(instance->tszInstanceName) ); + } + } + + if (appkey) RegCloseKey( appkey ); + if (hkey) RegCloseKey( hkey ); + + return autocenter; +} + BOOL device_instance_is_disabled( DIDEVICEINSTANCEW *instance, BOOL *override ) { static const WCHAR disabled_str[] = {'d', 'i', 's', 'a', 'b', 'l', 'e', 'd', 0}; @@ -2153,9 +2206,8 @@ 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->autocenter = device_instance_autocenter_initial( &device->instance ); 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) ); device->vtbl = vtbl;
diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 27d08fd2af2..dc7f8b25f70 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -2036,7 +2036,7 @@ HRESULT hid_joystick_create_device( struct dinput *dinput, const GUID *guid, IDi *out = NULL;
if (!(impl = calloc( 1, sizeof(*impl) ))) return E_OUTOFMEMORY; - dinput_device_init( &impl->base, &hid_joystick_vtbl, guid, dinput ); + InitializeCriticalSectionEx( &impl->base.crit, 0, RTL_CRITICAL_SECTION_FLAG_FORCE_DEBUG_INFO ); impl->base.crit.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": hid_joystick.base.crit"); impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND; impl->base.read_event = CreateEventW( NULL, TRUE, FALSE, NULL ); @@ -2050,6 +2050,7 @@ HRESULT hid_joystick_create_device( struct dinput *dinput, const GUID *guid, IDi hr = hid_joystick_device_try_open( impl->device_path, &impl->device, &impl->preparsed, &attrs, &impl->caps, &impl->base.instance, dinput->dwVersion ); } + dinput_device_init( &impl->base, &hid_joystick_vtbl, guid, dinput ); if (hr != DI_OK) goto failed;
impl->base.caps.dwDevType = impl->base.instance.dwDevType;
From: Tyson Whitehead twhitehead@gmail.com
Programs should check and/or set DIPROP_AUTOCENTER to ensure it is the value they want. Likely some do not though if the developers never tested on devices where the Windows' default wasn't correct for them. These programs will be broken under Wine if the Wine default is different than the Windows.
This commit logs a warning upon acquisition, if the program does it without first checking or setting DIPROP_AUTOCENTER. The warning tells the user what the default is, that it may be incorrect, and that they can override it with the new 'Autocenter' app key. --- dlls/dinput/device.c | 7 +++++++ dlls/dinput/device_private.h | 1 + 2 files changed, 8 insertions(+)
diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c index 2aa50a844f1..6985a6b279a 100644 --- a/dlls/dinput/device.c +++ b/dlls/dinput/device.c @@ -548,6 +548,10 @@ static HRESULT WINAPI dinput_device_Acquire( IDirectInputDevice8W *iface ) impl->status = STATUS_ACQUIRED; if (FAILED(hr = impl->vtbl->acquire( iface ))) impl->status = STATUS_UNACQUIRED; } + if (impl->autocenter_warning) + WARN( "Joystick %s was acquired without checking or setting autocenter, defaulting %s (override with 'Autocenter' registry key)\n", + debugstr_w(impl->instance.tszInstanceName), + impl->autocenter == DIPROPAUTOCENTER_ON ? "on" : "off" ); LeaveCriticalSection( &impl->crit ); if (hr != DI_OK) return hr;
@@ -1178,6 +1182,7 @@ static HRESULT dinput_device_get_property( IDirectInputDevice8W *iface, const GU DIPROPDWORD *value = (DIPROPDWORD *)header; if (!(impl->caps.dwFlags & DIDC_FORCEFEEDBACK)) return DIERR_UNSUPPORTED; value->dwData = impl->autocenter; + impl->autocenter_warning = FALSE; return DI_OK; } case (DWORD_PTR)DIPROP_BUFFERSIZE: @@ -1362,6 +1367,7 @@ static HRESULT dinput_device_set_property( IDirectInputDevice8W *iface, const GU const DIPROPDWORD *value = (const DIPROPDWORD *)header; if (!(impl->caps.dwFlags & DIDC_FORCEFEEDBACK)) return DIERR_UNSUPPORTED; impl->autocenter = value->dwData; + impl->autocenter_warning = FALSE; return DI_OK; } case (DWORD_PTR)DIPROP_FFGAIN: @@ -2206,6 +2212,7 @@ 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_warning = TRUE; device->autocenter = device_instance_autocenter_initial( &device->instance ); device->force_feedback_state = DIGFFS_STOPPED | DIGFFS_EMPTY; dinput_internal_addref( (device->dinput = dinput) ); diff --git a/dlls/dinput/device_private.h b/dlls/dinput/device_private.h index fa791f08a9d..a0da6c7d9f7 100644 --- a/dlls/dinput/device_private.h +++ b/dlls/dinput/device_private.h @@ -116,6 +116,7 @@ struct dinput_device BYTE device_state_report_id; BYTE device_state[DEVICE_STATE_MAX_SIZE];
+ BOOL autocenter_warning; BOOL autocenter; LONG device_gain; DWORD force_feedback_state;
From: Tyson Whitehead twhitehead@gmail.com
While ideally programs should check and turn autocenter off, it is likely that many do not. The default autocenter under Wine has been on. Tomasz Pakuta reported in !8744 that this is the wrong default for wheels and it has been causing a problem for many in the sim racing community.
This commit makes `DIPROPAUTOCENTER_OFF` the default value for `DIPROP_AUTOCENTER` unless the device name contains "joystick". Although this will miss some joysticks (those without joystick in their name, like the "Logitech WingMan"), it should hopefully be an overall win to have joysticks be the exception and not the default as I read there are more wheels than joysticks.
Wrongly classified devices can still be overriden by the new user app key "Autocenter". As in
[Software\Wine\DirectInput\Autocenter] "Device instance name"="on"
or
[Software\Wine\AppDefaults\app.exe\DirectInput\Autocenter] "Device instance name"="on" --- dlls/dinput/device.c | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-)
diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c index 6985a6b279a..543495d7a09 100644 --- a/dlls/dinput/device.c +++ b/dlls/dinput/device.c @@ -152,13 +152,21 @@ static BOOL device_instance_autocenter_initial( DIDEVICEINSTANCEW *instance ) static const WCHAR joystick_key[] = {'A', 'u', 't', 'o', 'c', 'e', 'n', 't', 'e', 'r', 0}; WCHAR buffer[MAX_PATH]; HKEY hkey, appkey, temp; - BOOL autocenter = DIPROPAUTOCENTER_ON; /* MS Sidewinder 2 initial value. No others known yet. There is - a project to collect other device defaults to improved this - https://github.com/twhitehead/issue-wine-ff-autocenter */ + BOOL autocenter = DIPROPAUTOCENTER_OFF;
- get_app_key( &hkey, &appkey ); + /* Default devices with joysticks in their name to on */ + wcscpy_s( buffer, sizeof(buffer)/sizeof(buffer[0]), instance->tszProductName ); + _wcslwr_s( buffer, sizeof(buffer)/sizeof(buffer[0]) ); + if ( wcsstr(buffer, L"joystick") != NULL ) + { + autocenter = DIPROPAUTOCENTER_ON; + } + TRACE( "Joystick '%s' autocenter default is %s.\n", debugstr_w(instance->tszInstanceName), + autocenter == DIPROPAUTOCENTER_ON ? "on" : "off" );
/* Autocenter settings are in the 'Autocenter' subkey */ + get_app_key( &hkey, &appkey ); + if (appkey) { if (RegOpenKeyW( appkey, joystick_key, &temp )) temp = 0; @@ -178,12 +186,12 @@ static BOOL device_instance_autocenter_initial( DIDEVICEINSTANCEW *instance ) { if (!wcscmp( on_str, buffer )) { - TRACE( "Joystick '%s' autocenter on based on registry key.\n", debugstr_w(instance->tszInstanceName) ); + TRACE( "Joystick '%s' autocenter default overridden to on based on registry key.\n", debugstr_w(instance->tszInstanceName) ); autocenter = DIPROPAUTOCENTER_ON; } else if (!wcscmp( off_str, buffer )) { - TRACE( "Joystick '%s' autocenter off based on registry key.\n", debugstr_w(instance->tszInstanceName) ); + TRACE( "Joystick '%s' autocenter default overridden to off based on registry key.\n", debugstr_w(instance->tszInstanceName) ); autocenter = DIPROPAUTOCENTER_OFF; } else
From: Tyson Whitehead twhitehead@gmail.com
Autocenter is turned off by default unless the device is a joystick. Joysticks were detected by looking for the word "joystick" in their name. This was less than ideal. This commit switches to checking if the`DIDEVICE_TYPE` is `DI8DEVTYPE_JOYSTICK` instead.
`DIDEVICE_TYPE` classification should be quite good for the for SDL and udev hidraw backends. For the udev input backends, there is no distinguishing of devices beyond `ID_INPUT_JOYSTICK`, which becomes `DI8DEVTYPE_GAMEPAD`, so autocenter is going to be always off.
Should udev implement a more granular classification than `ID_INPUT_JOYSTICK` this situation would improve. For now it can still be override by the user app key "Autocenter". --- dlls/dinput/device.c | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-)
diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c index 543495d7a09..b415eb7bbb5 100644 --- a/dlls/dinput/device.c +++ b/dlls/dinput/device.c @@ -154,15 +154,20 @@ static BOOL device_instance_autocenter_initial( DIDEVICEINSTANCEW *instance ) HKEY hkey, appkey, temp; BOOL autocenter = DIPROPAUTOCENTER_OFF;
- /* Default devices with joysticks in their name to on */ - wcscpy_s( buffer, sizeof(buffer)/sizeof(buffer[0]), instance->tszProductName ); - _wcslwr_s( buffer, sizeof(buffer)/sizeof(buffer[0]) ); - if ( wcsstr(buffer, L"joystick") != NULL ) + /* Autocenter default based on type (joystick is on, anything fancier is off) */ + switch ( GET_DIDEVICE_TYPE(instance->dwDevType) ) { + case DI8DEVTYPE_JOYSTICK: autocenter = DIPROPAUTOCENTER_ON; + break; + default: + autocenter = DIPROPAUTOCENTER_OFF; + break; } - TRACE( "Joystick '%s' autocenter default is %s.\n", debugstr_w(instance->tszInstanceName), - autocenter == DIPROPAUTOCENTER_ON ? "on" : "off" ); + TRACE( "Joystick '%s' autocenter default is %s due to device type 0x%02x.\n", + debugstr_w(instance->tszInstanceName), + autocenter == DIPROPAUTOCENTER_ON ? "on" : "off" , + GET_DIDEVICE_TYPE(instance->dwDevType) );
/* Autocenter settings are in the 'Autocenter' subkey */ get_app_key( &hkey, &appkey );
From: Tyson Whitehead twhitehead@gmail.com
--- dlls/joy.cpl/dinput.c | 71 ++++++++++++++++++++++++++++++++++++++ dlls/joy.cpl/joy.rc | 31 +++++++++-------- dlls/joy.cpl/joy_private.h | 3 ++ dlls/joy.cpl/main.c | 22 ++++++++---- dlls/joy.cpl/resource.h | 1 + 5 files changed, 107 insertions(+), 21 deletions(-)
diff --git a/dlls/joy.cpl/dinput.c b/dlls/joy.cpl/dinput.c index 68268cbb72c..cce3db3dbb8 100644 --- a/dlls/joy.cpl/dinput.c +++ b/dlls/joy.cpl/dinput.c @@ -198,6 +198,15 @@ static void clear_effects(void) static void set_selected_device( IDirectInputDevice8W *device ) { IDirectInputDevice8W *previous; + DIPROPDWORD ac_prop = { + .diph = { + .dwSize = sizeof(DIPROPDWORD), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwHow = DIPH_DEVICE, + }, + }; + HANDLE ac_button = GetDlgItem( dialog_hwnd, IDC_DI_AUTOCENTER ); + HRESULT hr;
EnterCriticalSection( &state_cs );
@@ -212,6 +221,16 @@ static void set_selected_device( IDirectInputDevice8W *device ) { IDirectInputDevice8_AddRef( device ); IDirectInputDevice8_SetEventNotification( device, state_event ); + hr = IDirectInputDevice8_GetProperty( device, DIPROP_AUTOCENTER, &ac_prop.diph); + if ( SUCCEEDED(hr) ) + { + EnableWindow( ac_button, TRUE); + SendMessageW( ac_button, BM_SETCHECK, ac_prop.dwData == DIPROPAUTOCENTER_ON, 0 ); + } + else + { + EnableWindow( ac_button, FALSE); + } IDirectInputDevice8_Acquire( device ); }
@@ -262,6 +281,52 @@ static void clear_devices(void) } }
+static void set_autocenter_initial( BOOL enabled ) +{ + IDirectInputDevice8W *device; + + if ( (device = get_selected_device()) ) + { + DIDEVICEINSTANCEW instance = { .dwSize = sizeof(DIDEVICEINSTANCEW) }; + HRESULT hr; + + if ( FAILED(hr = IDirectInputDevice8_GetDeviceInfo( device, &instance )) ) + { + WARN( "Unable to set autocenter default as get device info failed, hr %#lx\n", hr ); + } + else + { + DIPROPDWORD autocenter = { + .diph = { + .dwSize = sizeof(DIPROPDWORD), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwHow = DIPH_DEVICE, + }, + }; + HKEY defkey, appkey; + + get_app_key(L"Autocenter", &defkey, &appkey); + + if (!enabled) + set_config_key(defkey, appkey, instance.tszInstanceName, L"off"); + else + set_config_key(defkey, appkey, instance.tszInstanceName, L"on"); + + if (defkey) RegCloseKey(defkey); + if (appkey) RegCloseKey(appkey); + + IDirectInputDevice8_Unacquire( device ); + autocenter.dwData = enabled ? DIPROPAUTOCENTER_ON : DIPROPAUTOCENTER_OFF; + if ( FAILED(hr = IDirectInputDevice8_SetProperty(device, DIPROP_AUTOCENTER, &autocenter.diph)) ) + { + WARN( "Unable to set autocenter on device as set property failed, hr %#lx\n", hr ); + } + IDirectInputDevice8_Acquire( device ); + IDirectInputDevice8_Release( device ); + } + } +} + static DWORD WINAPI input_thread( void *param ) { HANDLE events[2] = {param, state_event}; @@ -783,6 +848,7 @@ INT_PTR CALLBACK test_di_dialog_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM .dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE, .dbcc_classguid = GUID_DEVINTERFACE_HID, }; + DWORD sel;
TRACE( "hwnd %p, msg %#x, wparam %#Ix, lparam %#Ix\n", hwnd, msg, wparam, lparam );
@@ -807,6 +873,11 @@ INT_PTR CALLBACK test_di_dialog_proc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM case MAKEWPARAM( IDC_DI_EFFECTS, LBN_SELCHANGE ): handle_di_effects_change( hwnd ); break; + + case MAKEWPARAM( IDC_DI_AUTOCENTER, BN_CLICKED ): + sel = SendMessageW( GetDlgItem( hwnd, IDC_DI_AUTOCENTER ), BM_GETCHECK, 0, 0 ); + set_autocenter_initial( sel ); + break; } return TRUE;
diff --git a/dlls/joy.cpl/joy.rc b/dlls/joy.cpl/joy.rc index 1eb62d86ab3..7ac6221ed1b 100644 --- a/dlls/joy.cpl/joy.rc +++ b/dlls/joy.cpl/joy.rc @@ -31,28 +31,28 @@ BEGIN IDS_CPL_INFO "Test and configure game controllers." END
-IDD_LIST DIALOG 0, 0, 320, 300 +IDD_LIST DIALOG 0, 0, 320, 310 STYLE WS_CAPTION | WS_CHILD | WS_DISABLED CAPTION "Joysticks" FONT 8, "Ms Shell Dlg" { PUSHBUTTON "&Disable", IDC_BUTTON_DI_DISABLE, 200, 20, 60, 15 PUSHBUTTON "&Reset", IDC_BUTTON_DI_RESET, 200, 40, 60, 15 - PUSHBUTTON "&Override", IDC_BUTTON_XI_OVERRIDE, 200, 90, 60, 15 - PUSHBUTTON "&Enable", IDC_BUTTON_ENABLE, 200, 160, 60, 15 + PUSHBUTTON "&Override", IDC_BUTTON_XI_OVERRIDE, 200, 95, 60, 15 + PUSHBUTTON "&Enable", IDC_BUTTON_ENABLE, 200, 170, 60, 15 LTEXT "Connected (DirectInput devices)", IDC_STATIC, 10, 10, 180, 10 - LISTBOX IDC_DI_ENABLED_LIST, 10, 20, 180, 60, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY - LTEXT "Connected (XInput devices)", IDC_STATIC, 10, 80, 180, 10 - LISTBOX IDC_XI_ENABLED_LIST, 10, 90, 180, 60, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY - LTEXT "Disabled", IDC_STATIC, 10, 150, 180, 10 - LISTBOX IDC_DISABLED_LIST, 10, 160, 180, 60, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY - - GROUPBOX "Advanced settings (restart prefix required to take effect)", IDC_ADVANCED, 10, 220, 300, 70 - AUTOCHECKBOX "Enable SDL", IDC_ENABLE_SDL, 20, 235, 100, 10 - AUTOCHECKBOX "Disable hidraw", IDC_DISABLE_HIDRAW, 20, 245, 100, 10 + LISTBOX IDC_DI_ENABLED_LIST, 10, 20, 180, 65, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY + LTEXT "Connected (XInput devices)", IDC_STATIC, 10, 85, 180, 10 + LISTBOX IDC_XI_ENABLED_LIST, 10, 95, 180, 65, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY + LTEXT "Disabled", IDC_STATIC, 10, 160, 180, 10 + LISTBOX IDC_DISABLED_LIST, 10, 170, 180, 65, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY + + GROUPBOX "Advanced settings (restart prefix required to take effect)", IDC_ADVANCED, 10, 235, 300, 65 + AUTOCHECKBOX "Enable SDL", IDC_ENABLE_SDL, 20, 250, 100, 10 + AUTOCHECKBOX "Disable hidraw", IDC_DISABLE_HIDRAW, 20, 260, 100, 10 }
-IDD_TEST_DI DIALOG 0, 0, 320, 300 +IDD_TEST_DI DIALOG 0, 0, 320, 310 STYLE WS_CAPTION | WS_CHILD | WS_DISABLED CAPTION "DInput" FONT 8, "Ms Shell Dlg" @@ -65,9 +65,10 @@ FONT 8, "Ms Shell Dlg" LISTBOX IDC_DI_EFFECTS, 15, 206, 291, 54, WS_TABSTOP | WS_VSCROLL | LBS_NOTIFY LTEXT "Press any button in the controller to activate the chosen effect. The effect direction can be changed with the controller axis.", IDC_STATIC, 15, 260, 291, 25 + AUTOCHECKBOX "Autocenter on for applications that don't set it", IDC_DI_AUTOCENTER, 15, 290, 291, 10 }
-IDD_TEST_XI DIALOG 0, 0, 320, 300 +IDD_TEST_XI DIALOG 0, 0, 320, 310 STYLE WS_CAPTION | WS_CHILD | WS_DISABLED CAPTION "XInput" FONT 8, "Ms Shell Dlg" @@ -100,7 +101,7 @@ FONT 8, "Ms Shell Dlg" AUTOCHECKBOX "Rumble", IDC_XI_RUMBLE_3, 260, 220, 40, 10 }
-IDD_TEST_WGI DIALOG 0, 0, 320, 300 +IDD_TEST_WGI DIALOG 0, 0, 320, 310 STYLE WS_CAPTION | WS_CHILD | WS_DISABLED CAPTION "Windows.Gaming.Input" FONT 8, "Ms Shell Dlg" diff --git a/dlls/joy.cpl/joy_private.h b/dlls/joy.cpl/joy_private.h index 7c02096dd55..0ae690b5944 100644 --- a/dlls/joy.cpl/joy_private.h +++ b/dlls/joy.cpl/joy_private.h @@ -31,6 +31,9 @@
#include "resource.h"
+extern BOOL get_app_key(const WCHAR *subkey_name, HKEY *defkey, HKEY *appkey); +extern DWORD set_config_key(HKEY defkey, HKEY appkey, const WCHAR *name, const WCHAR *value); + extern void paint_axes_view( HWND hwnd, UINT32 count, double *axes, const WCHAR **names ); extern void paint_povs_view( HWND hwnd, UINT32 count, UINT32 *povs ); extern void paint_buttons_view( HWND hwnd, UINT32 count, BYTE *buttons ); diff --git a/dlls/joy.cpl/main.c b/dlls/joy.cpl/main.c index d667ae6b5b0..baf0705b893 100644 --- a/dlls/joy.cpl/main.c +++ b/dlls/joy.cpl/main.c @@ -112,15 +112,25 @@ static void clear_devices(void) * get_app_key [internal] * Get the default DirectInput key and the selected app config key. */ -static BOOL get_app_key(HKEY *defkey, HKEY *appkey) +BOOL get_app_key(const WCHAR *subkey_name, HKEY *defkey, HKEY *appkey) { + HKEY tempkey; *appkey = 0;
/* Registry key can be found in HKCU\Software\Wine\DirectInput */ - if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\Wine\DirectInput\Joysticks", 0, NULL, 0, + if (RegCreateKeyExW(HKEY_CURRENT_USER, L"Software\Wine\DirectInput", 0, NULL, 0, KEY_SET_VALUE | KEY_READ, NULL, defkey, NULL)) *defkey = 0;
+ if ( *defkey && subkey_name ) + { + if ( RegCreateKeyExW( *defkey, subkey_name, 0, NULL, 0, + KEY_SET_VALUE | KEY_READ, NULL, &tempkey, NULL) ) + tempkey = 0; + RegCloseKey( *defkey ); + *defkey = tempkey; + } + return *defkey || *appkey; }
@@ -135,7 +145,7 @@ static BOOL get_advanced_key(HKEY *key) * set_config_key [internal] * Writes a string value to a registry key, deletes the key if value == NULL */ -static DWORD set_config_key(HKEY defkey, HKEY appkey, const WCHAR *name, const WCHAR *value) +DWORD set_config_key(HKEY defkey, HKEY appkey, const WCHAR *name, const WCHAR *value) { if (value == NULL) { @@ -168,7 +178,7 @@ static void enable_joystick(WCHAR *joy_name, BOOL enable) { HKEY hkey, appkey;
- get_app_key(&hkey, &appkey); + get_app_key(L"Joysticks", &hkey, &appkey);
if (!enable) set_config_key(hkey, appkey, joy_name, L"disabled"); @@ -238,7 +248,7 @@ static void refresh_joystick_list( HWND hwnd ) }
/* Search for disabled joysticks */ - get_app_key(&hkey, &appkey); + get_app_key(L"Joysticks", &hkey, &appkey); RegQueryInfoKeyW(hkey, NULL, NULL, NULL, NULL, NULL, NULL, &values, NULL, NULL, NULL, NULL);
for (i=0; i < values; i++) @@ -260,7 +270,7 @@ static void override_joystick(WCHAR *joy_name, BOOL override) { HKEY hkey, appkey;
- get_app_key(&hkey, &appkey); + get_app_key(L"Joysticks", &hkey, &appkey);
if (override) set_config_key(hkey, appkey, joy_name, L"override"); diff --git a/dlls/joy.cpl/resource.h b/dlls/joy.cpl/resource.h index 894fa016b66..9d7743249df 100644 --- a/dlls/joy.cpl/resource.h +++ b/dlls/joy.cpl/resource.h @@ -56,6 +56,7 @@ #define IDC_DI_POVS 2102 #define IDC_DI_BUTTONS 2103 #define IDC_DI_EFFECTS 2104 +#define IDC_DI_AUTOCENTER 2105
#define IDC_XI_USER_0 2200 #define IDC_XI_USER_1 2201
- adds an `Autocenter` registry app key that lets the default `DIPROP_AUTOCENTER` setting to be set on a per device and per application and device level,
FWIW per application management is usually a no-go in Wine, that is not something which can be supported. How does it work on Windows, are there any per-application profiles? I wasn't following the details, but can imagine that some backend limitations may justify certain per-device quirks, in that case those should be probably embedded in the code or pre-populated registry entries?
On Thu Aug 14 15:00:16 2025 +0000, Paul Gofman wrote:
- adds an `Autocenter` registry app key that lets the default
`DIPROP_AUTOCENTER` setting to be set on a per device and per application and device level, FWIW per application management is usually a no-go in Wine, that is not something which can be supported. How does it work on Windows, are there any per-application profiles? I wasn't following the details, but can imagine that some backend limitations may justify certain per-device quirks, in that case those should be probably embedded in the code or pre-populated registry entries?
I had assumed the initial value was always on, and hardcoded that in the code. Never though more about it until Tomasz said the setting was a driver thing and not a dinput thing and the sim racing community where having problems with autocenter being on on their devices. No idea if he had just concluded it must be a driver setting and not dinput or actually knew.
I plugged the Sidewinder 2 in under Windows and checked the driver and driver details under the control panel. It just shows _HID-compliant game controller_ and _USB Input Device_. The inf file is `input.inf` and the section is `HID_Raw_Inst.NT` matched on `HID_DEVICE_SYSTEM_GAME`. The instance path contained `VID_045E&PID_001B` so I searched the registry for that and `sidewinder`. Got hits (keys and subkeys) under `HKEY_USERS...` under * _System\CurrentControlSet\Control\MediaProperties\PrivateProperties\DirectInput\VID_045E&PID_001B_ * _System\CurrentControlSet\Control\MediaProperties\PrivateProperties\Joystick\OEM\VID_045E&PID_001B_ * _System\CurrentControlSet\Control\MediaResources\Joystick\DINPUT.DLL\JoystickSettings\VID_045E&PID_001B_ * _System\CurrentControlSet\Control\MediaResources\Joystick\DINPUT.DLL\CurrentJoystickSettings_
Here is a [complete dump](/uploads/5b8cfcab20a990d63904b29fd04af15f/user_registry.txt) for anyone interested in what the key, subkeys, and value are. Nothing really stands out to me.
With regard to application specific keys, I was initially thinking environment variable, so I poked around and grepped through the wine tree to see how things were done. Didn't seem nearly as popular as with proton, so I checked how the input backend options (SDL, udev input, or udev hidram) were done and discovered the [`app_key` routines](https://gitlab.winehq.org/wine/wine/-/blob/b822e25ed2d52c53df7d9474defc7b704...) ``` BOOL get_app_key(HKEY *defkey, HKEY *appkey) DWORD get_config_key( HKEY defkey, HKEY appkey, const WCHAR *name, WCHAR *buffer, DWORD size ) ``` Figured I should just use what was already there for consistency, and it was the least amount of code, so that is where that came from. For what it is worth, grepping for `app_key` shows it in use in the [unix loader code for dlloverrides](https://gitlab.winehq.org/wine/wine/-/blob/b822e25ed2d52c53df7d9474defc7b704...) too.
I expect the application specific bit will never get used and at most the global default might get set using the conrol panel `joy.cpl` (Game Controllers) application. Especially as under proton (where it will probably used the most) wine prefixes are generally per-application anyway. Or maybe it won't even be used at all now that this merge request switches the default to on only for classic joysticks and not sim specific hardware like wheels, yokes, etc. Although the udev input backend doesn't do a good classification job, so could see some use there.
Anyway, not at all attached to this way beyond having already written the code, and can change to something else if that is what is required. Just let me know what that something else is.
@rbernon effect playing in `joy.cpl` was broken at some point (e.g., the input thread doesn't update `DIJOYSTATE2 state` so checking `state` for button presses always fails, etc.). Have a few commits to fix it and clean stuff up a bit. Should I add them on top of this stack.
On Thu Aug 14 16:49:35 2025 +0000, Tyson Whitehead wrote:
@rbernon effect playing in `joy.cpl` was broken at some point (e.g., the input thread doesn't update `DIJOYSTATE2 state` so checking `state` for button presses always fails, etc.). Have a few commits to fix it and clean stuff up a bit. Should I add them on top of this stack.
Please make a separate MR, I'm not yet convinced about this one.
On Sun Aug 17 03:02:53 2025 +0000, Rémi Bernon wrote:
Please make a separate MR, I'm not yet convinced about this one.
Done. !8773.
As a side note, in working on the joy.cpl code I had a lot of issues with effects not playing back correctly randomly. Eventually tracked it down to the fact that the Sidewinder 2 occasionally returning garbage when allocating a slot. For example ``` ... 00b4:trace:hid:hid_internal_dispatch read feature report id 2 length 5: 00b4:trace:hid:hid_internal_dispatch 00000000 02 cc 03 00 00 ... 0144:fixme:joycpl:enum_effects Failed to create effect with type {13541c27-8e33-11d0-9ad0-00a0c9a06e35},flags 0xc804, hr 0x8007001e ``` where the usages are ``` ```
Seems the kernel driver knows about this and contains a [work around](https://github.com/torvalds/linux/blob/99bade344cfa1577c6dd658e10a3d64b119bd...) (it retries up to 60 times on an unknown response). Of course wine doesn't have such a work around o hidram randomly fails when loading effects.
I also located the simracing community discussions around autocenter that caused Tomasz to submit the merge request that removed `DIPROP_AUTOCENTER` support from the SDL and udev input backends. The discussions are in the Matrix Simracing chat group. There is a [link here](https://simracingonlinux.com/) * choose a web client, * signup for a matrix account, * join the Simracing channel (#simracer:matrix.org), and * search the history for autocenter.
and in this [Proton issue report](https://github.com/ValveSoftware/Proton/issues/8395#issuecomment-2857256211).
The issue was that the people would use a force feedback customization program like [Oversteer](https://github.com/berarma/oversteer) to setup their autocenter like they liked. Then when they started their game, the Wine SDL and udev input backends would make the autocenter API calls to turn it off or fully on, undoing whatever customization the individual had made. The workaround was to tweak the autocenter setting in Oversteer again while the program was running, which would cause Oversteer to write it's value again, overriding the Wine value.
So I don't think the initial value work I did in this patch is going to work for these people apart from those who just want it fully off, as it still only does fully on or fully off (and some devices have a bunch of other autocenter settings too, like the amount of damping mixed in to the spring). Fundamentally the problem is Wine and Oversteer colliding on the API.
I haven't thought a lot about it yet, but I think in an ideal world the driver would expose force feedback customization via the sysfs (as many are now) that would then be overlaid on top of the effects API (e.g., like a master/device volume control scales the application volume control).
Then customization programs like Oversteer could just work with the sysfs interface and not collide with programs using the effects API like Wine. This would support multiple customization programs as well as, unlike with force feedback API, customization programs could read the current state from sysfs and initialize their interface with that.