[PATCH v3 0/4] MR10364: dinput: Add support for generating consistent guidInstance values for devices.
This MR adds support for creating `guidInstance` values for joystick devices in a way that matches native behavior. Our current method of creating a `guidInstance` value uses the device's rawinput handle, which changes each time a controller is plugged in. Fixes a bug in Rocket League where a controller plug/unplug cycle results in a new controller being detected each time. The process is like this: - Controller is connected, the game gets its `guidInstance` value from `EnumDevices()`, stores it and associates it with player 1. - Controller is disconnected. - The same controller is reconnected, the game calls `EnumDevices()`, and finds a new `guidInstance` value. - It then assumes this is a new controller, creates a new player, and behaves as though player 1's controller is unplugged. This MR implements `guidInstance` in a way that matches native, and ensures that the `guidInstance` value remains consistent. Doing things this way also makes it easier to add support for `DIPROP_JOYSTICKID` and `GUID_Joystick` in a future MR. I've implemented those in a branch [here](https://gitlab.winehq.org/cmcadams/wine/-/tree/WIP/dinput-instance-guid-v08) using this system. -- v3: dinput: Return E_FAIL when joystick device is disconnected. dinput: Properly implement guidInstance for joystick devices. dinput/tests: Add tests for dinput joystick guidInstance values. dinput/tests: Fix check_device() test function when multiple HID devices are present. https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/tests/driver_hid.c | 33 +++++++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-) diff --git a/dlls/dinput/tests/driver_hid.c b/dlls/dinput/tests/driver_hid.c index 7b20af07493..f98ec2edb7b 100644 --- a/dlls/dinput/tests/driver_hid.c +++ b/dlls/dinput/tests/driver_hid.c @@ -42,8 +42,12 @@ static DRIVER_OBJECT *expect_driver; +static KSPIN_LOCK driver_data_lock; +static struct list device_list = LIST_INIT(device_list); + struct hid_device { + struct list entry; DEVICE_OBJECT *expect_bus_pdo; DEVICE_OBJECT *expect_hid_fdo; struct hid_device *expect_hid_ext; @@ -54,11 +58,27 @@ static void check_device( DEVICE_OBJECT *device ) { HID_DEVICE_EXTENSION *ext = device->DeviceExtension; struct hid_device *impl = ext->MiniDeviceExtension; + KIRQL irql; ok( device == impl->expect_hid_fdo, "got device %p\n", device ); ok( device->DriverObject == expect_driver, "got DriverObject %p\n", device->DriverObject ); - if (!device->NextDevice) ok( device == impl->expect_hid_fdo, "got device %p\n", device ); - else ok( device->NextDevice == impl->expect_hid_fdo, "got NextDevice %p\n", device->NextDevice ); + + KeAcquireSpinLock( &driver_data_lock, &irql ); + if (!device->NextDevice) + { + struct hid_device *impl_tail = LIST_ENTRY( list_tail(&device_list), struct hid_device, entry ); + + ok( device == impl_tail->expect_hid_fdo, "got device %p\n", device ); + } + else + { + struct hid_device *next_impl = LIST_ENTRY( list_next(&device_list, &impl->entry), struct hid_device, entry ); + HID_DEVICE_EXTENSION *next_ext = device->NextDevice->DeviceExtension; + struct hid_device *next_ext_impl = next_ext->MiniDeviceExtension; + + ok( next_impl == next_ext_impl, "got NextDevice %p.\n", device->NextDevice ); + } + KeReleaseSpinLock( &driver_data_lock, irql ); ok( !device->AttachedDevice, "got AttachedDevice %p\n", device->AttachedDevice ); ok( ext->MiniDeviceExtension == impl->expect_hid_ext, "got MiniDeviceExtension %p\n", ext->MiniDeviceExtension ); @@ -113,6 +133,7 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) HID_DEVICE_EXTENSION *ext = device->DeviceExtension; struct hid_device *impl = ext->MiniDeviceExtension; ULONG code = stack->MinorFunction; + KIRQL irql; if (winetest_debug > 1) trace( "%s: device %p, code %#lx %s\n", __func__, device, code, debugstr_pnp(code) ); @@ -126,6 +147,9 @@ static NTSTATUS WINAPI driver_pnp( DEVICE_OBJECT *device, IRP *irp ) IoSetDeviceInterfaceState( &impl->control_symlink, FALSE ); RtlFreeUnicodeString( &impl->control_symlink ); irp->IoStatus.Status = STATUS_SUCCESS; + KeAcquireSpinLock( &driver_data_lock, &irql ); + list_remove( &impl->entry ); + KeReleaseSpinLock( &driver_data_lock, irql ); break; case IRP_MN_STOP_DEVICE: case IRP_MN_SURPRISE_REMOVAL: @@ -229,12 +253,16 @@ static NTSTATUS WINAPI driver_add_device( DRIVER_OBJECT *driver, DEVICE_OBJECT * struct hid_device *impl = ext->MiniDeviceExtension; DEVICE_OBJECT *bus_pdo = ext->PhysicalDeviceObject; NTSTATUS status; + KIRQL irql; if (winetest_debug > 1) trace( "%s: driver %p, device %p\n", __func__, driver, device ); impl->expect_hid_fdo = device; impl->expect_bus_pdo = ext->PhysicalDeviceObject; impl->expect_hid_ext = ext->MiniDeviceExtension; + KeAcquireSpinLock( &driver_data_lock, &irql ); + list_add_head( &device_list, &impl->entry ); + KeReleaseSpinLock( &driver_data_lock, irql ); todo_wine ok( impl->expect_bus_pdo->AttachedDevice == device, "got AttachedDevice %p\n", bus_pdo->AttachedDevice ); @@ -286,6 +314,7 @@ NTSTATUS WINAPI DriverEntry( DRIVER_OBJECT *driver, UNICODE_STRING *registry ) NTSTATUS status; expect_driver = driver; + KeInitializeSpinLock( &driver_data_lock ); if ((status = winetest_init())) return status; if (winetest_debug > 1) trace( "%s: driver %p\n", __func__, driver ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/tests/dinput_test.h | 6 + dlls/dinput/tests/hid.c | 115 +++++++-- dlls/dinput/tests/hotplug.c | 19 +- dlls/dinput/tests/joystick8.c | 442 ++++++++++++++++++++++++++++++++ 4 files changed, 565 insertions(+), 17 deletions(-) diff --git a/dlls/dinput/tests/dinput_test.h b/dlls/dinput/tests/dinput_test.h index d19e1868d76..58030710310 100644 --- a/dlls/dinput/tests/dinput_test.h +++ b/dlls/dinput/tests/dinput_test.h @@ -64,9 +64,15 @@ void cleanup_registry_keys(void); void dinput_test_init_( const char *file, int line ); void dinput_test_exit(void); +HRESULT dinput_test_create_device_instance( DWORD version, const GUID *guid_inst, IDirectInputDevice8W **device ); HRESULT dinput_test_create_device( DWORD version, DIDEVICEINSTANCEW *devinst, IDirectInputDevice8W **device ); DWORD WINAPI dinput_test_device_thread( void *stop_event ); +#define dinput_test_get_device_hid_serial_string( a, b ) \ + dinput_test_get_device_hid_serial_string_( __FILE__, __LINE__, a, b ) +void dinput_test_get_device_hid_serial_string_( const char *file, int line, IDirectInputDevice8W *device, + WCHAR *serial_out ); + #define fill_context( a, b ) fill_context_( __FILE__, __LINE__, a, b ) void fill_context_( const char *file, int line, char *buffer, SIZE_T size ); diff --git a/dlls/dinput/tests/hid.c b/dlls/dinput/tests/hid.c index 273e94e82bb..7e40509e7e9 100644 --- a/dlls/dinput/tests/hid.c +++ b/dlls/dinput/tests/hid.c @@ -3600,13 +3600,36 @@ done: hid_device_stop( &desc, 1 ); } +static BOOL cleanup_test_device_keys(HKEY key, const WCHAR *path) +{ + WCHAR buf[MAX_PATH]; + BOOL ret = FALSE; + unsigned int i; + HKEY root_key; + DWORD size; + + RegCreateKeyExW( key, path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL ); + size = ARRAY_SIZE(buf); + for (i = 0; !RegEnumKeyExW( root_key, i, buf, &size, NULL, NULL, NULL, NULL ); i++) + { + if (!wcsnicmp( buf, L"VID_1209", 8 )) + { + RegDeleteTreeW( root_key, buf ); + ret = TRUE; + break; + } + size = ARRAY_SIZE(buf); + } + RegCloseKey( root_key ); + return ret; +} + void cleanup_registry_keys(void) { static const WCHAR joystick_oem_path[] = L"System\\CurrentControlSet\\Control\\MediaProperties\\" "PrivateProperties\\Joystick\\OEM"; static const WCHAR dinput_path[] = L"System\\CurrentControlSet\\Control\\MediaProperties\\" "PrivateProperties\\DirectInput"; - HKEY root_key; /* These keys are automatically created by DInput and they store the list of supported force-feedback effects. OEM drivers are supposed @@ -3616,21 +3639,10 @@ void cleanup_registry_keys(void) We need to clean them up, or DInput will not refresh the list of effects from the PID report changes. */ - RegCreateKeyExW( HKEY_CURRENT_USER, joystick_oem_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL ); - RegDeleteTreeW( root_key, expect_vidpid_str ); - RegCloseKey( root_key ); - - RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL ); - RegDeleteTreeW( root_key, expect_vidpid_str ); - RegCloseKey( root_key ); - - RegCreateKeyExW( HKEY_LOCAL_MACHINE, joystick_oem_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL ); - RegDeleteTreeW( root_key, expect_vidpid_str ); - RegCloseKey( root_key ); - - RegCreateKeyExW( HKEY_LOCAL_MACHINE, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL ); - RegDeleteTreeW( root_key, expect_vidpid_str ); - RegCloseKey( root_key ); + while (cleanup_test_device_keys( HKEY_CURRENT_USER, joystick_oem_path )) {} + while (cleanup_test_device_keys( HKEY_CURRENT_USER, dinput_path )) {} + while (cleanup_test_device_keys( HKEY_LOCAL_MACHINE, joystick_oem_path )) {} + while (cleanup_test_device_keys( HKEY_LOCAL_MACHINE, dinput_path )) {} } static LRESULT CALLBACK monitor_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) @@ -3763,6 +3775,44 @@ void dinput_test_exit(void) CoUninitialize(); } +HRESULT dinput_test_create_device_instance( DWORD version, const GUID *guid_inst, IDirectInputDevice8W **device ) +{ + IDirectInput8W *di8; + IDirectInputW *di; + HRESULT hr; + ULONG ref; + + *device = NULL; + if (version >= 0x800) + { + hr = DirectInput8Create( instance, version, &IID_IDirectInput8W, (void **)&di8, NULL ); + if (FAILED(hr)) + { + win_skip( "DirectInput8Create returned %#lx\n", hr ); + return hr; + } + + hr = IDirectInput8_CreateDevice( di8, guid_inst, device, NULL ); + ref = IDirectInput8_Release( di8 ); + ok( ref == 0, "Release returned %ld\n", ref ); + } + else + { + hr = DirectInputCreateEx( instance, version, &IID_IDirectInput2W, (void **)&di, NULL ); + if (FAILED(hr)) + { + win_skip( "DirectInputCreateEx returned %#lx\n", hr ); + return hr; + } + + hr = IDirectInput_CreateDevice( di, guid_inst, (IDirectInputDeviceW **)device, NULL ); + ref = IDirectInput_Release( di ); + ok( ref == 0, "Release returned %ld\n", ref ); + } + + return hr; +} + BOOL CALLBACK find_test_device( const DIDEVICEINSTANCEW *devinst, void *context ) { if (IsEqualGUID( &devinst->guidProduct, &expect_guid_product )) @@ -3832,6 +3882,39 @@ HRESULT dinput_test_create_device( DWORD version, DIDEVICEINSTANCEW *devinst, ID return DI_OK; } +void dinput_test_get_device_hid_serial_string_( const char *file, int line, IDirectInputDevice8W *device, + WCHAR *serial_out) +{ + DIPROPGUIDANDPATH prop_guid_path = + { + .diph = + { + .dwSize = sizeof(DIPROPGUIDANDPATH), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwHow = DIPH_DEVICE, + }, + }; + WCHAR device_id[MAX_PATH] = { 0 }; + HANDLE file_handle; + HRESULT hr; + BOOL ret; + + serial_out[0] = 0; + hr = IDirectInputDevice8_GetProperty( device, DIPROP_GUIDANDPATH, &prop_guid_path.diph ); + ok_(file, line)( hr == S_OK, "Unexpected hr %#lx.\n", hr ); + if (FAILED(hr)) return; + + file_handle = CreateFileW( prop_guid_path.wszPath, FILE_READ_ACCESS | FILE_WRITE_ACCESS, + FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL ); + ok_(file, line)( file_handle != INVALID_HANDLE_VALUE, "Got error %lu.\n", GetLastError() ); + if (file_handle == INVALID_HANDLE_VALUE) return; + + ret = HidD_GetSerialNumberString( file_handle, device_id, ARRAY_SIZE(device_id) ); + ok_(file, line)( ret, "Failed to get HID serial number string.\n" ); + if (ret) wcscpy( serial_out, device_id ); + CloseHandle( file_handle ); +} + DWORD WINAPI dinput_test_device_thread( void *stop_event ) { #include "psh_hid_macros.h" diff --git a/dlls/dinput/tests/hotplug.c b/dlls/dinput/tests/hotplug.c index ce44bae754c..350050c0a42 100644 --- a/dlls/dinput/tests/hotplug.c +++ b/dlls/dinput/tests/hotplug.c @@ -156,8 +156,9 @@ static BOOL test_input_lost( DWORD version ) DIDEVICEINSTANCEW devinst = {.dwSize = sizeof(DIDEVICEINSTANCEW)}; DIDEVICEOBJECTDATA objdata[32] = {{0}}; - IDirectInputDevice8W *device = NULL; + IDirectInputDevice8W *device, *device2; ULONG ref, count, size; + GUID guid_instance; DIJOYSTATE2 state; HRESULT hr; @@ -169,6 +170,7 @@ static BOOL test_input_lost( DWORD version ) memcpy( desc.report_descriptor_buf, report_desc, sizeof(report_desc) ); fill_context( desc.context, ARRAY_SIZE(desc.context) ); + device = device2 = NULL; if (!hid_device_start( &desc, 1 )) goto done; if (FAILED(hr = dinput_test_create_device( version, &devinst, &device ))) goto done; @@ -220,6 +222,21 @@ static BOOL test_input_lost( DWORD version ) ref = IDirectInputDevice8_Release( device ); ok( ref == 0, "Release returned %ld\n", ref ); + /* Test guidInstance across hotplugs. It should remain the same. */ + guid_instance = devinst.guidInstance; + memset( &devinst, 0, sizeof(devinst) ); + devinst.dwSize = sizeof(DIDEVICEINSTANCEW); + hr = dinput_test_create_device( version, &devinst, &device2 ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( !!device2, "device2 is NULL.\n" ); + if (device2) + { + todo_wine ok( !memcmp( &guid_instance, &devinst.guidInstance, sizeof(guid_instance) ), + "Unexpected guidInstance.\n" ); + ref = IDirectInputDevice8_Release( device2 ); + ok( ref == 0, "Release returned %ld\n", ref ); + } + done: hid_device_stop( &desc, 1 ); cleanup_registry_keys(); diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index d267aff724f..d1b7a195fae 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -5981,6 +5981,444 @@ static void test_rawinput_desktop( const char *path, BOOL input ) DestroyWindow( hwnd ); } +static void init_hid_device_desc(struct hid_device_desc *desc, const WCHAR *serial_str, WORD pid) +{ +#include "psh_hid_macros.h" + static const unsigned char report_desc[] = + { + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + USAGE(1, HID_USAGE_GENERIC_JOYSTICK), + COLLECTION(1, Application), + USAGE(1, HID_USAGE_GENERIC_JOYSTICK), + COLLECTION(1, Physical), + USAGE_PAGE(1, HID_USAGE_PAGE_BUTTON), + USAGE_MINIMUM(1, 1), + USAGE_MAXIMUM(1, 6), + LOGICAL_MINIMUM(1, 0), + LOGICAL_MAXIMUM(1, 1), + PHYSICAL_MINIMUM(1, 0), + PHYSICAL_MAXIMUM(1, 1), + REPORT_SIZE(1, 1), + REPORT_COUNT(1, 8), + INPUT(1, Data|Var|Abs), + END_COLLECTION, + END_COLLECTION, + }; + C_ASSERT(sizeof(report_desc) < MAX_HID_DESCRIPTOR_LEN); +#include "pop_hid_macros.h" + struct hid_device_desc init_desc = + { + .use_report_id = TRUE, + .caps = { .InputReportByteLength = 1 }, + .attributes = default_attributes, + }; + + *desc = init_desc; + desc->report_descriptor_len = sizeof(report_desc); + memcpy( desc->report_descriptor_buf, report_desc, sizeof(report_desc) ); + fill_context( desc->context, ARRAY_SIZE(desc->context) ); + if (serial_str) wcscpy(desc->serial_str, serial_str); + desc->attributes.ProductID = pid; +} + +struct joystick_test_device +{ + struct hid_device_desc desc; + BOOL started; +}; + +static BOOL start_joystick_test_device(struct joystick_test_device *device) +{ + device->started = hid_device_start( &device->desc, 1 ); + return device->started; +} + +static void stop_joystick_test_device(struct joystick_test_device *device) +{ + if (device->started) hid_device_stop( &device->desc, 1 ); + device->started = FALSE; +} + +struct dinput_test_devices +{ + DIDEVICEINSTANCEW *instances; + unsigned int instances_size; + unsigned int instances_count; +}; + +static void dinput_test_devices_struct_init(struct dinput_test_devices *devices, DIDEVICEINSTANCEW *insts, + unsigned int insts_size) +{ + const DIDEVICEINSTANCEW devinst_init = { .dwSize = sizeof(DIDEVICEINSTANCEW) }; + unsigned int i; + + for (i = 0; i < insts_size; i++) insts[i] = devinst_init; + devices->instances = insts; + devices->instances_size = insts_size; + devices->instances_count = 0; +} + +BOOL CALLBACK find_test_devices( const DIDEVICEINSTANCEW *devinst, void *context ) +{ + struct dinput_test_devices *devices = (struct dinput_test_devices *)context; + + if (!memcmp(devinst->guidProduct.Data4, expect_guid_product.Data4, sizeof(expect_guid_product.Data4)) + && (LOWORD(devinst->guidProduct.Data1) == LOWORD(EXPECT_VIDPID))) + { + devices->instances[devices->instances_count++] = *devinst; + if (devices->instances_count >= devices->instances_size) return DIENUM_STOP; + } + return DIENUM_CONTINUE; +} + +static HRESULT dinput_test_enum_devices( DWORD version, struct dinput_test_devices *context ) +{ + IDirectInput8W *di8; + IDirectInputW *di; + HRESULT hr; + ULONG ref; + + context->instances_count = 0; + if (version >= 0x800) + { + hr = DirectInput8Create( instance, version, &IID_IDirectInput8W, (void **)&di8, NULL ); + if (FAILED(hr)) + { + win_skip( "DirectInput8Create returned %#lx\n", hr ); + return hr; + } + + hr = IDirectInput8_EnumDevices( di8, DI8DEVCLASS_ALL, find_test_devices, context, DIEDFL_ALLDEVICES ); + ok( hr == DI_OK, "EnumDevices returned: %#lx\n", hr ); + + ref = IDirectInput8_Release( di8 ); + ok( ref == 0, "Release returned %ld\n", ref ); + } + else + { + hr = DirectInputCreateEx( instance, version, &IID_IDirectInput2W, (void **)&di, NULL ); + if (FAILED(hr)) + { + win_skip( "DirectInputCreateEx returned %#lx\n", hr ); + return hr; + } + + hr = IDirectInput_EnumDevices( di, 0, find_test_devices, context, DIEDFL_ALLDEVICES ); + ok( hr == DI_OK, "EnumDevices returned: %#lx\n", hr ); + + ref = IDirectInput_Release( di ); + ok( ref == 0, "Release returned %ld\n", ref ); + } + + return DI_OK; +} + +static void test_joystick_instance_guid( DWORD version ) +{ + struct dinput_test_devices dinput_test_devs; + struct joystick_test_device test_devs[4]; + WCHAR device_serial[MAX_PATH]; + DIDEVICEINSTANCEW devinsts[4]; + IDirectInputDevice8W *device; + GUID guid_instances[4]; + unsigned int i; + HRESULT hr; + + winetest_push_context( "%#lx", version ); + cleanup_registry_keys(); + + memset( test_devs, 0, sizeof(test_devs) ); + init_hid_device_desc( &test_devs[0].desc, L"1&2345&6", 0x0001 ); + init_hid_device_desc( &test_devs[1].desc, L"2&7812&1", 0x0001 ); + init_hid_device_desc( &test_devs[2].desc, L"3&4580&7", 0x0002 ); + init_hid_device_desc( &test_devs[3].desc, L"4&5880&3", 0x0002 ); + + for (i = 0; i < ARRAY_SIZE(test_devs); i++) + { + if (!start_joystick_test_device( &test_devs[i] )) + goto done; + } + + /* Enumerate all devices, each with a unique guidInstance. */ + dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); + hr = dinput_test_enum_devices( version, &dinput_test_devs ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( dinput_test_devs.instances_count == 4, "Unexpected count %u.\n", dinput_test_devs.instances_count ); + for (i = 0; i < dinput_test_devs.instances_count; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + guid_instances[i] = devinsts[i].guidInstance; + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[i].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Delete the existing registry keys, this will result in a new set of + * guidInstance values being generated on the next call to EnumDevices(). + */ + cleanup_registry_keys(); + + /* + * EnumDevices() hasn't been called yet, guidInstance values are still + * valid. + */ + for (i = 0; i < dinput_test_devs.instances_count; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[i].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Call EnumDevices(), a new set of guidInstance values and registry entries + * will be created. + */ + dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); + hr = dinput_test_enum_devices( version, &dinput_test_devs ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( dinput_test_devs.instances_count == 4, "Unexpected count %u.\n", dinput_test_devs.instances_count ); + for (i = 0; i < dinput_test_devs.instances_count; i++) + { + winetest_push_context( "device %d", i ); + + todo_wine ok( !IsEqualGUID( &guid_instances[i], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", + debugstr_guid(&devinsts[i].guidInstance) ); + + /* Old guidInstance no longer works. */ + device = NULL; + hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); + todo_wine ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + if (device) IDirectInputDevice8_Release(device); + guid_instances[i] = devinsts[i].guidInstance; + + /* New guidInstance, same device. */ + hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[i].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Stop devices 0 and 2. After enumeration, their guidInstance values will + * be assigned to devices 1 and 3. + */ + stop_joystick_test_device( &test_devs[0] ); + stop_joystick_test_device( &test_devs[2] ); + + /* Stopped devices should return E_FAIL. */ + hr = dinput_test_create_device_instance( version, &devinsts[0].guidInstance, &device ); + todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + hr = dinput_test_create_device_instance( version, &devinsts[2].guidInstance, &device ); + todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + + /* After calling EnumDevices(), guidInstance values will be reassigned. */ + dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); + hr = dinput_test_enum_devices( version, &dinput_test_devs ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( dinput_test_devs.instances_count == 2, "Unexpected count %u.\n", dinput_test_devs.instances_count ); + for (i = 0; i < dinput_test_devs.instances_count; i++) + { + const unsigned int expected_joystick = !i ? 1 : 3; + + winetest_push_context( "device %d", i ); + + todo_wine ok( IsEqualGUID( &guid_instances[expected_joystick - 1], &devinsts[i].guidInstance ), + "Unexpected guidInstance %s.\n", debugstr_guid(&devinsts[i].guidInstance) ); + hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[expected_joystick].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Restart devices 0 and 2, they should get their old guidInstance values + * back after another call to EnumDevices(). + */ + if (!start_joystick_test_device( &test_devs[0] )) goto done; + if (!start_joystick_test_device( &test_devs[2] )) goto done; + + for (i = 0; i < dinput_test_devs.instances_count; i++) + { + const unsigned int expected_joystick = !i ? 1 : 3; + + winetest_push_context( "device %d", i ); + + todo_wine ok( IsEqualGUID( &guid_instances[expected_joystick - 1], &devinsts[i].guidInstance ), + "Unexpected guidInstance %s.\n", debugstr_guid(&devinsts[i].guidInstance) ); + hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[expected_joystick].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* guidInstance values are all back to what they were. */ + dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); + hr = dinput_test_enum_devices( version, &dinput_test_devs ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( dinput_test_devs.instances_count == 4, "Unexpected count %u.\n", dinput_test_devs.instances_count ); + for (i = 0; i < dinput_test_devs.instances_count; i++) + { + winetest_push_context( "device %d", i ); + + todo_wine_if(!(i & 0x1)) ok( IsEqualGUID( &guid_instances[i], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", + debugstr_guid(&devinsts[i].guidInstance) ); + + hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[i].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* Stop all devices. */ + for (i = 0; i < ARRAY_SIZE(test_devs); i++) stop_joystick_test_device( &test_devs[i] ); + + /* + * Call EnumDevices() with all devices stopped. Test behavior of device + * creation without calling EnumDevices() with the devices present + * beforehand. + */ + dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); + hr = dinput_test_enum_devices( version, &dinput_test_devs ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( !dinput_test_devs.instances_count, "Unexpected count %u.\n", dinput_test_devs.instances_count ); + for (i = 0; i < ARRAY_SIZE(test_devs); i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); + ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + + /* + * Start the joystick device. This will succeed, even without having + * called EnumDevices() beforehand. + */ + if (!start_joystick_test_device( &test_devs[i] )) goto done; + hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); + todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + if (device) + { + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[i].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + } + stop_joystick_test_device( &test_devs[i] ); + + hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); + todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + + if (!start_joystick_test_device( &test_devs[i] )) goto done; + winetest_pop_context(); + } + + for (i = 0; i < ARRAY_SIZE(test_devs); i++) stop_joystick_test_device( &test_devs[i] ); + + /* Reset device list. */ + dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); + hr = dinput_test_enum_devices( version, &dinput_test_devs ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( !dinput_test_devs.instances_count, "Unexpected count %u.\n", dinput_test_devs.instances_count ); + + /* + * Start device 0, create a device with guidInstance 0. + * Stop device 0. + * Start device 1, attempt to create a device with guidInstance 1. + * Fails. + * Try to create a device with guidInstance 0, which is now associated + * with device 1. + */ + if (!start_joystick_test_device( &test_devs[0] )) goto done; + + hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); + todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + if (device) + { + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[0].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + } + stop_joystick_test_device( &test_devs[0] ); + + hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); + todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + + /* Start device 1. */ + if (!start_joystick_test_device( &test_devs[1] )) goto done; + + /* + * This still fails with E_FAIL, as guid_instances[0] is still associated + * with device 0. + */ + hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); + todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + + /* + * guid_instances[1] is not currently associated with a device, which + * means attempting to use this GUID results in what seems to be an + * internal call to EnumDevices(). With device 0 currently stopped, + * guid_instances[0] gets reassigned to device 1. + */ + hr = dinput_test_create_device_instance( version, &guid_instances[1], &device ); + ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + + hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); + todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + if (device) + { + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[1].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + + IDirectInputDevice8_Release(device); + } + stop_joystick_test_device( &test_devs[1] ); + +done: + for (i = 0; i < ARRAY_SIZE(test_devs); i++) + stop_joystick_test_device( &test_devs[i] ); + cleanup_registry_keys(); + winetest_pop_context(); +} + START_TEST( joystick8 ) { char **argv; @@ -6008,6 +6446,10 @@ START_TEST( joystick8 ) test_simple_joystick( 0x700 ); test_simple_joystick( 0x800 ); + test_joystick_instance_guid( 0x500 ); + test_joystick_instance_guid( 0x700 ); + test_joystick_instance_guid( 0x800 ); + test_many_axes_joystick(); test_driving_wheel_axes(); test_rawinput( argv ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/dinput_main.c | 1 + dlls/dinput/dinput_private.h | 1 + dlls/dinput/joystick_hid.c | 444 +++++++++++++++++++++++++++++----- dlls/dinput/tests/hotplug.c | 11 +- dlls/dinput/tests/joystick8.c | 40 ++- 5 files changed, 412 insertions(+), 85 deletions(-) diff --git a/dlls/dinput/dinput_main.c b/dlls/dinput/dinput_main.c index e4bc1f7961d..aabad4ff97a 100644 --- a/dlls/dinput/dinput_main.c +++ b/dlls/dinput/dinput_main.c @@ -522,6 +522,7 @@ BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, void *reserved ) break; case DLL_PROCESS_DETACH: if (reserved) break; + hid_joystick_cleanup(); unregister_di_em_win_class(); break; } diff --git a/dlls/dinput/dinput_private.h b/dlls/dinput/dinput_private.h index 0609b816b3b..7e7b1e490f5 100644 --- a/dlls/dinput/dinput_private.h +++ b/dlls/dinput/dinput_private.h @@ -56,6 +56,7 @@ extern HRESULT keyboard_enum_device( DWORD type, DWORD flags, DIDEVICEINSTANCEW extern HRESULT keyboard_create_device( struct dinput *dinput, const GUID *guid, IDirectInputDevice8W **out ); extern HRESULT hid_joystick_enum_device( DWORD type, DWORD flags, DIDEVICEINSTANCEW *instance, DWORD version, int index ); extern HRESULT hid_joystick_create_device( struct dinput *dinput, const GUID *guid, IDirectInputDevice8W **out ); +extern void hid_joystick_cleanup( void ); struct DevicePlayer { GUID instance_guid; diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 22709c4bfd9..32ef69eb899 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -49,9 +49,320 @@ WINE_DEFAULT_DEBUG_CHANNEL(dinput); DEFINE_GUID( GUID_DEVINTERFACE_WINEXINPUT,0x6c53d5fd,0x6480,0x440f,0xb6,0x18,0x47,0x67,0x50,0xc5,0xe1,0xa6 ); -DEFINE_GUID( hid_joystick_guid, 0x9e573edb, 0x7734, 0x11d2, 0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf7 ); DEFINE_GUID( device_path_guid, 0x00000000, 0x0000, 0x0000, 0x8d, 0x4a, 0x23, 0x90, 0x3f, 0xb6, 0xbd, 0xf8 ); +/* + * Version 1 UUID timestamps are a count of the number of 100 nanosecond + * intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian + * reform to the Christian calendar). FILETIME is a count of the number of 100 + * nanosecond intervals since 00:00:00.00, 1 January 1601. In order to convert + * a FILETIME value to a UUID timestamp, we need to add: + * - 17 days in October 1582. + * - 30 days in November 1582. + * - 31 days in December 1582. + * - 18 years between January 1583 and January 1601. + * - 5 leap days in those 18 years. + */ +#define UUID_TIME_TO_SYSTEM_TIME_DAYS ((ULONG64)((365 * 18) + 5 + 17 + 30 + 31)) +#define UUID_TIME_TO_SYSTEM_TIME_NS_DIFFERENCE ((UUID_TIME_TO_SYSTEM_TIME_DAYS) * 24 * 60 * 60 * 10000000) +DEFINE_GUID( dinput_joystick_uuid_init, 0x00000000, 0x0000, 0x1000, 0x80, 0x00, 0x00, 0x00, 'D', 'E', 'S', 'T' ); +static void create_v1_uuid(GUID *guid) +{ + GUID tmp = dinput_joystick_uuid_init; + static LONG clock_seq; + ULARGE_INTEGER time; + ULONG cur_seq; + + GetSystemTimeAsFileTime( (FILETIME *)&time ); + time.QuadPart += UUID_TIME_TO_SYSTEM_TIME_NS_DIFFERENCE; + tmp.Data1 = (time.QuadPart & 0xffffffff); + tmp.Data2 = ((time.QuadPart >> 32) & 0xffff); + tmp.Data3 |= ((time.QuadPart >> 48) & 0x0fff); + cur_seq = InterlockedIncrement( &clock_seq ); + tmp.Data4[1] |= (cur_seq & 0xff); + tmp.Data4[0] |= ((cur_seq & 0x3f00) >> 8); + *guid = tmp; +} + +static HANDLE dinput_reg_mutex; +static BOOL WINAPI dinput_init_reg_mutex(INIT_ONCE *once, void *param, void **ctx) +{ + HANDLE mutex = CreateMutexW( NULL, FALSE, L"__wine_dinput_reg_mutex" ); + + if (mutex) dinput_reg_mutex = mutex; + + return !!mutex; +} + +static void get_dinput_reg_mutex( void ) +{ + static INIT_ONCE once = INIT_ONCE_STATIC_INIT; + + if (!dinput_reg_mutex) InitOnceExecuteOnce( &once, dinput_init_reg_mutex, NULL, NULL ); + WaitForSingleObject( dinput_reg_mutex, INFINITE ); +} + +static void release_dinput_reg_mutex( void ) +{ + assert( dinput_reg_mutex ); + ReleaseMutex( dinput_reg_mutex ); +} + +/* Need to enter the mutex prior to accessing these registry entries. */ +static const WCHAR *dinput_path = L"System\\CurrentControlSet\\Control\\MediaProperties\\" + "PrivateProperties\\DirectInput"; +static void reset_joystick_instances_in_registry(void) +{ + WCHAR buf[MAX_PATH]; + unsigned int i, j; + HKEY root_key; + DWORD size; + + if (RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) return; + + size = ARRAY_SIZE(buf); + for (i = 0; !RegEnumKeyExW( root_key, i, buf, &size, NULL, NULL, NULL, NULL ); i++) + { + HKEY dev_key; + + size = ARRAY_SIZE(buf); + wcscat( buf, L"\\Calibration" ); + if (RegOpenKeyExW( root_key, buf, 0, KEY_ALL_ACCESS, &dev_key )) continue; + + for (j = 0; !RegEnumKeyExW( dev_key, j, buf, &size, NULL, NULL, NULL, NULL ); j++) + { + size = ARRAY_SIZE(buf); + RegDeleteKeyValueW( dev_key, buf, L"DevicePath" ); + } + + size = ARRAY_SIZE(buf); + RegCloseKey( dev_key ); + } + RegCloseKey( root_key ); +} + +static HKEY get_empty_joystick_instance_in_registry(WORD vid, WORD pid) +{ + HKEY root_key, dev_key = NULL; + WCHAR buf[MAX_PATH]; + DWORD len, size; + unsigned int i; + LSTATUS ret; + + swprintf( buf, ARRAY_SIZE(buf), L"%s\\VID_%04X&PID_%04X\\Calibration", dinput_path, vid, pid ); + if (RegCreateKeyExW( HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) + { + ERR( "Failed to create key.\n" ); + return NULL; + } + + size = ARRAY_SIZE(buf); + for (i = 0; !RegEnumKeyExW( root_key, i, buf, &size, NULL, NULL, NULL, NULL ); i++) + { + size = ARRAY_SIZE(buf); + if ((ret = RegGetValueW( root_key, buf, L"DevicePath", RRF_RT_REG_SZ, NULL, NULL, &len ))) + { + if (ret != ERROR_FILE_NOT_FOUND) WARN( "Got unexpected error %#lx.\n", ret ); + if (!RegOpenKeyExW( root_key, buf, 0, KEY_ALL_ACCESS, &dev_key )) break; + } + } + + if (!dev_key) + { + swprintf( buf, ARRAY_SIZE(buf), L"%s\\VID_%04X&PID_%04X\\Calibration\\%d", dinput_path, vid, pid, i ); + if ((ret = RegCreateKeyExW( HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &dev_key, NULL ))) + WARN( "Got unexpected error %#lx when creating key.\n", ret ); + } + RegCloseKey( root_key ); + return dev_key; +} + +static void set_joystick_instance_in_registry(WORD vid, WORD pid, const WCHAR *dev_path) +{ + HKEY dev_key = get_empty_joystick_instance_in_registry( vid, pid ); + LSTATUS ret; + DWORD len; + + if (!dev_key) + { + ERR( "Failed to get device instance in registry.\n" ); + return; + } + + /* If the instance GUID doesn't exist already, create it. */ + if ((ret = RegGetValueW( dev_key, NULL, L"GUID", RRF_RT_REG_BINARY, NULL, NULL, &len ))) + { + GUID tmp; + + if (ret != ERROR_FILE_NOT_FOUND) WARN( "Got unexpected error %#lx.\n", ret ); + + create_v1_uuid( &tmp ); + if ((ret = RegSetValueExW( dev_key, L"GUID", 0, REG_BINARY, (const BYTE *)&tmp, + sizeof(tmp) ))) + WARN( "Failed to set instance GUID value in registry with error %#lx.\n", ret ); + } + + if ((ret = RegSetValueExW( dev_key, L"DevicePath", 0, REG_SZ, (const BYTE *)dev_path, + (wcslen(dev_path) + 1) * sizeof(WCHAR) ))) + WARN( "Failed to set device path in registry with error %#lx.\n", ret ); + + RegCloseKey( dev_key ); +} + +static CRITICAL_SECTION joystick_crit; +static CRITICAL_SECTION_DEBUG joystick_critsect_debug = +{ + 0, 0, &joystick_crit, + { &joystick_critsect_debug.ProcessLocksList, &joystick_critsect_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": joystick_crit") } +}; +static CRITICAL_SECTION joystick_crit = { &joystick_critsect_debug, -1, 0, 0, 0, 0 }; + +/* Need to enter the CS to access this list. */ +static struct list joystick_list = LIST_INIT(joystick_list); + +struct joystick_instance +{ + struct list entry; + WORD vid, pid; + GUID guid; + + WCHAR dev_path[MAX_PATH]; +}; + +static void dinput_update_joystick_instances_from_registry(void) +{ + struct joystick_instance *cur, *cur2; + WCHAR buf[MAX_PATH]; + unsigned int i, j; + HKEY root_key; + DWORD size; + + EnterCriticalSection( &joystick_crit ); + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &joystick_list, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + free( cur ); + } + + get_dinput_reg_mutex(); + if (RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) goto exit; + + size = ARRAY_SIZE(buf); + for (i = 0; !RegEnumKeyExW( root_key, i, buf, &size, NULL, NULL, NULL, NULL ); i++) + { + WORD vid, pid; + HKEY dev_key; + + size = ARRAY_SIZE(buf); + if (swscanf( buf, L"VID_%04X&PID_%04X", &vid, &pid ) != 2) continue; + + wcscat( buf, L"\\Calibration" ); + if (RegOpenKeyExW( root_key, buf, 0, KEY_ALL_ACCESS, &dev_key )) continue; + + for (j = 0; !RegEnumKeyExW( dev_key, j, buf, &size, NULL, NULL, NULL, NULL ); j++) + { + struct joystick_instance *inst; + DWORD len; + + size = ARRAY_SIZE(buf); + if (RegGetValueW( dev_key, buf, L"DevicePath", RRF_RT_REG_SZ, NULL, NULL, &len )) continue; + + if (!(inst = calloc( 1, sizeof(*inst) ))) + { + ERR( "Failed to allocate memory for device instance.\n" ); + continue; + } + + inst->vid = vid; + inst->pid = pid; + len = sizeof(inst->guid); + if (!RegGetValueW( dev_key, buf, L"GUID", RRF_RT_REG_BINARY, NULL, &inst->guid, &len )) + { + len = sizeof(inst->dev_path); + if (RegGetValueW( dev_key, buf, L"DevicePath", RRF_RT_REG_SZ, NULL, inst->dev_path, &len )) + { + ERR( "Failed to get device path from key.\n" ); + free(inst); + continue; + } + } + else + { + ERR( "Failed to get guidInstance from key.\n" ); + free(inst); + continue; + } + list_add_tail( &joystick_list, &inst->entry ); + } + RegCloseKey( dev_key ); + } + +exit: + RegCloseKey( root_key ); + release_dinput_reg_mutex(); + LeaveCriticalSection( &joystick_crit ); +} + +static BOOL dinput_get_joystick_instance_from_guid(const GUID *guid, WCHAR *dev_path, GUID *out_guid) +{ + struct joystick_instance *inst; + BOOL ret_val = FALSE; + + EnterCriticalSection( &joystick_crit ); + LIST_FOR_EACH_ENTRY( inst, &joystick_list, struct joystick_instance, entry ) + { + GUID guid_product = dinput_pidvid_guid; + + guid_product.Data1 = MAKELONG( inst->vid, inst->pid); + if (IsEqualGUID( &inst->guid, guid ) || IsEqualGUID( &guid_product, guid )) + { + wcscpy( dev_path, inst->dev_path ); + *out_guid = inst->guid; + ret_val = TRUE; + break; + } + } + LeaveCriticalSection( &joystick_crit ); + + return ret_val; +} + +static BOOL dinput_get_joystick_instance_from_idx(unsigned int idx, WCHAR *dev_path, GUID *out_guid) +{ + struct joystick_instance *inst; + BOOL ret_val = FALSE; + unsigned int i = 0; + + EnterCriticalSection( &joystick_crit ); + LIST_FOR_EACH_ENTRY( inst, &joystick_list, struct joystick_instance, entry ) + { + if (i == idx) + { + wcscpy( dev_path, inst->dev_path ); + *out_guid = inst->guid; + ret_val = TRUE; + break; + } + i++; + } + LeaveCriticalSection( &joystick_crit ); + + return ret_val; +} + +void hid_joystick_cleanup( void ) +{ + struct joystick_instance *cur, *cur2; + + if (dinput_reg_mutex) CloseHandle( dinput_reg_mutex ); + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &joystick_list, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + free( cur ); + } +} + struct pid_control_report { BYTE id; @@ -1421,13 +1732,12 @@ static HRESULT hid_joystick_device_try_open( const WCHAR *path, HANDLE *device, BOOL has_accelerator, has_brake, has_clutch, has_z, has_pov; PHIDP_PREPARSED_DATA preparsed_data = NULL; HIDP_LINK_COLLECTION_NODE nodes[256]; - DWORD type, size, button_count = 0; + DWORD type, button_count = 0; HIDP_BUTTON_CAPS buttons[10]; HIDP_VALUE_CAPS value; HANDLE device_file; ULONG node_count; NTSTATUS status; - UINT32 handle; USHORT count; device_file = CreateFileW( path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, @@ -1450,14 +1760,7 @@ static HRESULT hid_joystick_device_try_open( const WCHAR *path, HANDLE *device, if (!HidD_GetProductString( device_file, instance->tszInstanceName, MAX_PATH * sizeof(WCHAR) )) goto failed; if (!HidD_GetProductString( device_file, instance->tszProductName, MAX_PATH * sizeof(WCHAR) )) goto failed; - if (!DeviceIoControl( device_file, IOCTL_HID_GET_WINE_RAWINPUT_HANDLE, NULL, 0, &handle, sizeof(handle), &size, NULL )) - { - ERR( "failed to get raw input handle, error %lu\n", GetLastError() ); - goto failed; - } - - instance->guidInstance = hid_joystick_guid; - instance->guidInstance.Data1 ^= handle; + instance->guidInstance = GUID_NULL; instance->guidProduct = dinput_pidvid_guid; instance->guidProduct.Data1 = MAKELONG( attrs->VendorID, attrs->ProductID ); instance->guidFFDriver = GUID_NULL; @@ -1572,21 +1875,23 @@ failed: return DIERR_DEVICENOTREG; } -static HRESULT hid_joystick_device_open( int index, const GUID *guid, DIDEVICEINSTANCEW *instance, - WCHAR *device_path, HANDLE *device, PHIDP_PREPARSED_DATA *preparsed, - HIDD_ATTRIBUTES *attrs, HIDP_CAPS *caps, DWORD version ) +static HRESULT hid_joystick_refresh_devices( void ) { char buffer[sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W) + MAX_PATH * sizeof(WCHAR)]; SP_DEVICE_INTERFACE_DETAIL_DATA_W *detail = (void *)buffer; SP_DEVICE_INTERFACE_DATA iface = {.cbSize = sizeof(iface)}; SP_DEVINFO_DATA devinfo = {.cbSize = sizeof(devinfo)}; WCHAR device_id[MAX_PATH], *tmp; + PHIDP_PREPARSED_DATA preparsed; + HIDD_ATTRIBUTES attrs = { 0 }; HDEVINFO set, xi_set; + HIDP_CAPS caps; + HANDLE device; BOOL override; UINT32 i = 0; GUID hid; - TRACE( "index %d, guid %s\n", index, debugstr_guid( guid ) ); + TRACE( "\n" ); HidD_GetHidGuid( &hid ); @@ -1594,18 +1899,23 @@ static HRESULT hid_joystick_device_open( int index, const GUID *guid, DIDEVICEIN if (set == INVALID_HANDLE_VALUE) return DIERR_DEVICENOTREG; xi_set = SetupDiGetClassDevsW( &GUID_DEVINTERFACE_WINEXINPUT, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT ); - *device = NULL; - *preparsed = NULL; + device = NULL; + preparsed = NULL; + + get_dinput_reg_mutex(); + reset_joystick_instances_in_registry(); while (SetupDiEnumDeviceInterfaces( set, NULL, &hid, i++, &iface )) { + DIDEVICEINSTANCEW cur_instance = {.dwSize = sizeof(DIDEVICEINSTANCEW)}; + detail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W); if (!SetupDiGetDeviceInterfaceDetailW( set, &iface, detail, sizeof(buffer), NULL, &devinfo )) continue; - if (FAILED(hid_joystick_device_try_open( detail->DevicePath, device, preparsed, - attrs, caps, instance, version ))) + if (FAILED(hid_joystick_device_try_open( detail->DevicePath, &device, &preparsed, + &attrs, &caps, &cur_instance, 0x0800 ))) continue; - if (device_instance_is_disabled( instance, &override )) + if (device_instance_is_disabled( &cur_instance, &override )) goto next; if (override && SetupDiGetDeviceInstanceIdW( set, &devinfo, device_id, MAX_PATH, NULL ) && @@ -1619,58 +1929,67 @@ static HRESULT hid_joystick_device_open( int index, const GUID *guid, DIDEVICEIN if (!SetupDiGetDeviceInterfaceDetailW( xi_set, &iface, detail, sizeof(buffer), NULL, &devinfo )) goto next; - CloseHandle( *device ); - HidD_FreePreparsedData( *preparsed ); - if (FAILED(hid_joystick_device_try_open( detail->DevicePath, device, preparsed, - attrs, caps, instance, version ))) + CloseHandle( device ); + HidD_FreePreparsedData( preparsed ); + if (FAILED(hid_joystick_device_try_open( detail->DevicePath, &device, &preparsed, + &attrs, &caps, &cur_instance, 0x0800 ))) continue; } - /* enumerate device by GUID */ - if (IsEqualGUID( guid, &instance->guidProduct ) || IsEqualGUID( guid, &instance->guidInstance )) break; - - /* enumerate all devices */ - if (index >= 0 && !index--) break; - + set_joystick_instance_in_registry(attrs.VendorID, attrs.ProductID, detail->DevicePath); next: - CloseHandle( *device ); - HidD_FreePreparsedData( *preparsed ); - *device = NULL; - *preparsed = NULL; + CloseHandle( device ); + HidD_FreePreparsedData( preparsed ); + device = NULL; + preparsed = NULL; } + release_dinput_reg_mutex(); if (xi_set != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList( xi_set ); SetupDiDestroyDeviceInfoList( set ); - if (!*device || !*preparsed) return DIERR_DEVICENOTREG; - - lstrcpynW( device_path, detail->DevicePath, MAX_PATH ); + dinput_update_joystick_instances_from_registry(); return DI_OK; } HRESULT hid_joystick_enum_device( DWORD type, DWORD flags, DIDEVICEINSTANCEW *instance, DWORD version, int index ) { - HIDD_ATTRIBUTES attrs = {.Size = sizeof(attrs)}; - PHIDP_PREPARSED_DATA preparsed; - WCHAR device_path[MAX_PATH]; - GUID guid = GUID_NULL; - HIDP_CAPS caps; - HANDLE device; - HRESULT hr; + WCHAR device_path[MAX_PATH] = { 0 }; + HRESULT hr = DI_OK; + BOOL found = FALSE; + GUID guid; TRACE( "type %#lx, flags %#lx, instance %p, version %#lx, index %d\n", type, flags, instance, version, index ); - hr = hid_joystick_device_open( index, &guid, instance, device_path, &device, &preparsed, - &attrs, &caps, version ); + if (!index) hr = hid_joystick_refresh_devices(); if (hr != DI_OK) return hr; - HidD_FreePreparsedData( preparsed ); - CloseHandle( device ); + if (dinput_get_joystick_instance_from_idx( index, device_path, &guid )) + { + DIDEVICEINSTANCEW dev_inst = {.dwSize = sizeof(DIDEVICEINSTANCEW)}; + PHIDP_PREPARSED_DATA preparsed = NULL; + HIDD_ATTRIBUTES attrs = { 0 }; + HIDP_CAPS caps; + HANDLE device; - TRACE( "found device %s, usage %04x:%04x, product %s, instance %s, name %s\n", debugstr_w(device_path), - instance->wUsagePage, instance->wUsage, debugstr_guid( &instance->guidProduct ), - debugstr_guid( &instance->guidInstance ), debugstr_w(instance->tszInstanceName) ); + if (SUCCEEDED(hid_joystick_device_try_open( device_path, &device, &preparsed, + &attrs, &caps, &dev_inst, version ))) + { + HidD_FreePreparsedData( preparsed ); + CloseHandle( device ); - return DI_OK; + dev_inst.guidInstance = guid; + *instance = dev_inst; + found = TRUE; + } + } + + if (!found) hr = DIERR_DEVICENOTREG; + if (hr == DI_OK) + TRACE( "found device %s, usage %04x:%04x, product %s, instance %s, name %s\n", debugstr_w(device_path), + instance->wUsagePage, instance->wUsage, debugstr_guid( &instance->guidProduct ), + debugstr_guid( &instance->guidInstance ), debugstr_w(instance->tszInstanceName) ); + + return hr; } static BOOL init_object_properties( struct dinput_device *device, UINT index, struct hid_value_caps *caps, @@ -2040,15 +2359,28 @@ HRESULT hid_joystick_create_device( struct dinput *dinput, const GUID *guid, IDi impl->base.dwCoopLevel = DISCL_NONEXCLUSIVE | DISCL_BACKGROUND; impl->base.read_event = CreateEventW( NULL, TRUE, FALSE, NULL ); - if (memcmp( device_path_guid.Data4, guid->Data4, sizeof(device_path_guid.Data4) )) - hr = hid_joystick_device_open( -1, guid, &impl->base.instance, impl->device_path, &impl->device, &impl->preparsed, - &attrs, &impl->caps, dinput->dwVersion ); - else + if (!memcmp( device_path_guid.Data4, guid->Data4, sizeof(device_path_guid.Data4) )) { wcscpy( impl->device_path, *(const WCHAR **)guid ); hr = hid_joystick_device_try_open( impl->device_path, &impl->device, &impl->preparsed, &attrs, &impl->caps, &impl->base.instance, dinput->dwVersion ); } + else + { + GUID guid_inst; + + /* If we don't get a device for this GUID initially, try refreshing. */ + if (!dinput_get_joystick_instance_from_guid( guid, impl->device_path, &guid_inst )) + hid_joystick_refresh_devices(); + if (dinput_get_joystick_instance_from_guid( guid, impl->device_path, &guid_inst )) + { + hr = hid_joystick_device_try_open( impl->device_path, &impl->device, &impl->preparsed, &attrs, + &impl->caps, &impl->base.instance, dinput->dwVersion ); + if (hr == DI_OK) impl->base.instance.guidInstance = guid_inst; + } + else + hr = DIERR_DEVICENOTREG; + } if (hr != DI_OK) goto failed; impl->base.caps.dwDevType = impl->base.instance.dwDevType; diff --git a/dlls/dinput/tests/hotplug.c b/dlls/dinput/tests/hotplug.c index 350050c0a42..e47fd768c18 100644 --- a/dlls/dinput/tests/hotplug.c +++ b/dlls/dinput/tests/hotplug.c @@ -229,13 +229,10 @@ static BOOL test_input_lost( DWORD version ) hr = dinput_test_create_device( version, &devinst, &device2 ); ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); ok( !!device2, "device2 is NULL.\n" ); - if (device2) - { - todo_wine ok( !memcmp( &guid_instance, &devinst.guidInstance, sizeof(guid_instance) ), - "Unexpected guidInstance.\n" ); - ref = IDirectInputDevice8_Release( device2 ); - ok( ref == 0, "Release returned %ld\n", ref ); - } + ok( !memcmp( &guid_instance, &devinst.guidInstance, sizeof(guid_instance) ), + "Unexpected guidInstance.\n" ); + ref = IDirectInputDevice8_Release( device2 ); + ok( ref == 0, "Release returned %ld\n", ref ); done: hid_device_stop( &desc, 1 ); diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index d1b7a195fae..656a9d1693c 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6197,13 +6197,13 @@ static void test_joystick_instance_guid( DWORD version ) { winetest_push_context( "device %d", i ); - todo_wine ok( !IsEqualGUID( &guid_instances[i], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", + ok( !IsEqualGUID( &guid_instances[i], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", debugstr_guid(&devinsts[i].guidInstance) ); /* Old guidInstance no longer works. */ device = NULL; hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); - todo_wine ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); if (device) IDirectInputDevice8_Release(device); guid_instances[i] = devinsts[i].guidInstance; @@ -6243,7 +6243,7 @@ static void test_joystick_instance_guid( DWORD version ) winetest_push_context( "device %d", i ); - todo_wine ok( IsEqualGUID( &guid_instances[expected_joystick - 1], &devinsts[i].guidInstance ), + ok( IsEqualGUID( &guid_instances[expected_joystick - 1], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", debugstr_guid(&devinsts[i].guidInstance) ); hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); @@ -6269,7 +6269,7 @@ static void test_joystick_instance_guid( DWORD version ) winetest_push_context( "device %d", i ); - todo_wine ok( IsEqualGUID( &guid_instances[expected_joystick - 1], &devinsts[i].guidInstance ), + ok( IsEqualGUID( &guid_instances[expected_joystick - 1], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", debugstr_guid(&devinsts[i].guidInstance) ); hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); @@ -6291,7 +6291,7 @@ static void test_joystick_instance_guid( DWORD version ) { winetest_push_context( "device %d", i ); - todo_wine_if(!(i & 0x1)) ok( IsEqualGUID( &guid_instances[i], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", + ok( IsEqualGUID( &guid_instances[i], &devinsts[i].guidInstance ), "Unexpected guidInstance %s.\n", debugstr_guid(&devinsts[i].guidInstance) ); hr = dinput_test_create_device_instance( version, &devinsts[i].guidInstance, &device ); @@ -6330,7 +6330,7 @@ static void test_joystick_instance_guid( DWORD version ) */ if (!start_joystick_test_device( &test_devs[i] )) goto done; hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); if (device) { dinput_test_get_device_hid_serial_string(device, device_serial); @@ -6367,15 +6367,13 @@ static void test_joystick_instance_guid( DWORD version ) if (!start_joystick_test_device( &test_devs[0] )) goto done; hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); - if (device) - { - dinput_test_get_device_hid_serial_string(device, device_serial); - ok( !wcscmp(test_devs[0].desc.serial_str, device_serial), "Unexpected device serial %s.\n", - debugstr_w(device_serial) ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[0].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + IDirectInputDevice8_Release(device); - IDirectInputDevice8_Release(device); - } stop_joystick_test_device( &test_devs[0] ); hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); @@ -6401,15 +6399,13 @@ static void test_joystick_instance_guid( DWORD version ) ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); - if (device) - { - dinput_test_get_device_hid_serial_string(device, device_serial); - ok( !wcscmp(test_devs[1].desc.serial_str, device_serial), "Unexpected device serial %s.\n", - debugstr_w(device_serial) ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + dinput_test_get_device_hid_serial_string(device, device_serial); + ok( !wcscmp(test_devs[1].desc.serial_str, device_serial), "Unexpected device serial %s.\n", + debugstr_w(device_serial) ); + IDirectInputDevice8_Release(device); - IDirectInputDevice8_Release(device); - } stop_joystick_test_device( &test_devs[1] ); done: -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 2 +- dlls/dinput/tests/joystick8.c | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 32ef69eb899..0b01be90fde 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -1742,7 +1742,7 @@ static HRESULT hid_joystick_device_try_open( const WCHAR *path, HANDLE *device, device_file = CreateFileW( path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 ); - if (device_file == INVALID_HANDLE_VALUE) return DIERR_DEVICENOTREG; + if (device_file == INVALID_HANDLE_VALUE) return E_FAIL; if (!HidD_GetPreparsedData( device_file, &preparsed_data )) goto failed; if (!HidD_GetAttributes( device_file, attrs )) goto failed; diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 656a9d1693c..a0a0cb08f9d 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6228,9 +6228,9 @@ static void test_joystick_instance_guid( DWORD version ) /* Stopped devices should return E_FAIL. */ hr = dinput_test_create_device_instance( version, &devinsts[0].guidInstance, &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); hr = dinput_test_create_device_instance( version, &devinsts[2].guidInstance, &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); /* After calling EnumDevices(), guidInstance values will be reassigned. */ dinput_test_devices_struct_init( &dinput_test_devs, devinsts, ARRAY_SIZE(devinsts) ); @@ -6342,7 +6342,7 @@ static void test_joystick_instance_guid( DWORD version ) stop_joystick_test_device( &test_devs[i] ); hr = dinput_test_create_device_instance( version, &guid_instances[i], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); if (!start_joystick_test_device( &test_devs[i] )) goto done; winetest_pop_context(); @@ -6377,7 +6377,7 @@ static void test_joystick_instance_guid( DWORD version ) stop_joystick_test_device( &test_devs[0] ); hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); /* Start device 1. */ if (!start_joystick_test_device( &test_devs[1] )) goto done; @@ -6387,7 +6387,7 @@ static void test_joystick_instance_guid( DWORD version ) * with device 0. */ hr = dinput_test_create_device_instance( version, &guid_instances[0], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); /* * guid_instances[1] is not currently associated with a device, which -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
On Tue Mar 31 11:24:05 2026 +0000, Rémi Bernon wrote:
I've send some of the straightforward changes to a separate MR, splitting some of the commits into related changes. I'm still looking at the registry code. It seems a bit complicated still, and would be nice to have something simpler but I'm not yet sure how. I've rebased on top of your changes, thanks for doing that. :)
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10364#note_134502
Fwiw I think we can simply drop the tests. The complexity to support multiple child windows (and I think this is only when the device id is used, because there are tests already with multiple child device created and they seem to pass) doesn't seem worth it. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10364#note_134686
On Tue Mar 31 11:24:05 2026 +0000, Connor McAdams wrote:
I've rebased on top of your changes, thanks for doing that. :) So yeah, I think it could be improved. First it would be nice to split the change in at least two parts: first part could simply update the module local cache and registry, and second part would pivot to using the cached data to enumerate / open joysticks.
To that end I think moving the error cases to `dinput.c`, would be nicer. You would have one `hid_joystick_refresh_device` in two places, one when `hid_joystick_create_device` returns `DIERR_DEVICENOTREG` then call it again, and another in `EnumDevices` before enumerating HID joysticks. This would match nicely with the `hid_joystick_cleanup_devices` call in `dinput.c` as well. Then regarding the cache update I don't think it has to reuse the existing enumeration loop. It doesn't need all the complicated filtering and checks, and it's mostly just a matter of, within the global mutex, 1) reading the registry to get an up to date current device list, 2) enumerating the HID devices, 3) adding anything that's missing from the list, then 4) saving it back to the registry. I think it could also be a good opportunity to switch the device enumeration to `cfgmgr32` which is simpler to use. In the second part, `hid_joystick_device_open` could just be switched over to using the local list instead of setupapi. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10364#note_134688
I've split the tests into https://gitlab.winehq.org/wine/wine/-/merge_requests/10538, with a couple extra and some general simplification. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10364#note_134689
participants (3)
-
Connor McAdams -
Connor McAdams (@cmcadams) -
Rémi Bernon (@rbernon)