[PATCH v2 0/4] MR10755: dinput: Add proper support for DIPROP_JOYSTICKID and GUID_Joystick.
This was also attempted in MR !6878. -- v2: dinput: Add support for GUID_Joystick. dinput/tests: Add tests for GUID_Joystick. dinput: Add support for DIPROP_JOYSTICKID. https://gitlab.winehq.org/wine/wine/-/merge_requests/10755
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/tests/dinput_test.h | 1 + dlls/dinput/tests/driver_hid.h | 2 +- dlls/dinput/tests/hid.c | 41 ++++ dlls/dinput/tests/joystick8.c | 357 ++++++++++++++++++++++++++++++++ 4 files changed, 400 insertions(+), 1 deletion(-) diff --git a/dlls/dinput/tests/dinput_test.h b/dlls/dinput/tests/dinput_test.h index ae3416c4ba7..64325ee2b32 100644 --- a/dlls/dinput/tests/dinput_test.h +++ b/dlls/dinput/tests/dinput_test.h @@ -59,6 +59,7 @@ void bus_device_stop(void); BOOL find_hid_device_path( WCHAR *device_path ); void cleanup_registry_keys(void); +BOOL set_joystick_id_for_guid_instance(const GUID *guid_instance, DWORD joystick_id); #define dinput_test_init() dinput_test_init_( __FILE__, __LINE__ ) void dinput_test_init_( const char *file, int line ); diff --git a/dlls/dinput/tests/driver_hid.h b/dlls/dinput/tests/driver_hid.h index 941e7296719..06cbd77f6dc 100644 --- a/dlls/dinput/tests/driver_hid.h +++ b/dlls/dinput/tests/driver_hid.h @@ -213,7 +213,7 @@ int winetest_start_time, winetest_last_time; int winetest_mute_threshold = 42; static KSPIN_LOCK tls_data_lock; -static struct winetest_thread_data tls_data_pool[128]; +static struct winetest_thread_data tls_data_pool[256]; static HANDLE tls_data_thread[ARRAY_SIZE(tls_data_pool)]; static DWORD tls_data_count; diff --git a/dlls/dinput/tests/hid.c b/dlls/dinput/tests/hid.c index 7c42ac54f74..86b1640baf4 100644 --- a/dlls/dinput/tests/hid.c +++ b/dlls/dinput/tests/hid.c @@ -3650,6 +3650,47 @@ void cleanup_registry_keys(void) cleanup_registry_tree( HKEY_LOCAL_MACHINE, dinput_path ); } +static HKEY find_registry_key_for_guid_instance(const GUID *guid_instance) +{ + static const WCHAR dinput_path[] = L"System\\CurrentControlSet\\Control\\MediaProperties\\" + "PrivateProperties\\DirectInput"; + HKEY root_key, tmp, dev_key = NULL; + WCHAR buf[MAX_PATH]; + + RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL ); + for (ULONG i = 0, size = ARRAY_SIZE(buf); !RegEnumKeyExW( root_key, i, buf, &size, NULL, NULL, NULL, NULL ); i++, size = ARRAY_SIZE(buf)) + { + wcscat( buf, L"\\Calibration" ); + RegCreateKeyExW( root_key, buf, 0, NULL, 0, KEY_READ, NULL, &tmp, NULL ); + for (ULONG j = 0, size = ARRAY_SIZE(buf); !RegEnumKeyExW( tmp, j, buf, &size, NULL, NULL, NULL, NULL ); j++, size = ARRAY_SIZE(buf)) + { + GUID guid = { 0 }; + DWORD len; + + len = sizeof(guid); + if (!RegGetValueW( tmp, buf, L"GUID", RRF_RT_REG_BINARY, NULL, &guid, &len) + && !memcmp(&guid, guid_instance, sizeof(*guid_instance))) + RegCreateKeyExW( tmp, buf, 0, NULL, 0, KEY_SET_VALUE | KEY_QUERY_VALUE, NULL, &dev_key, NULL ); + if (dev_key) break; + } + RegCloseKey( tmp ); + tmp = NULL; + if (dev_key) break; + } + RegCloseKey( root_key ); + return dev_key; +} + +BOOL set_joystick_id_for_guid_instance(const GUID *guid_instance, DWORD joystick_id) +{ + HKEY instance_key = find_registry_key_for_guid_instance(guid_instance); + + if (!instance_key) return FALSE; + RegSetValueExW(instance_key, L"Joystick Id", 0, REG_BINARY, (const BYTE *)&joystick_id, sizeof(joystick_id)); + RegCloseKey( instance_key ); + return TRUE; +} + static LRESULT CALLBACK monitor_wndproc( HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam ) { if (msg == WM_DEVICECHANGE) diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 562dc87e62a..96109776e52 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6469,6 +6469,359 @@ done: winetest_pop_context(); } +#define check_device_joystick_id( a, b, c ) check_device_joystick_id_( __LINE__, a, b, c ) +static void check_device_joystick_id_( unsigned int line, IDirectInputDevice8W *device, DWORD joystick_id, BOOL todo ) +{ + DIPROPDWORD prop_dword = + { + .diph = + { + .dwSize = sizeof(DIPROPDWORD), + .dwHeaderSize = sizeof(DIPROPHEADER), + .dwHow = DIPH_DEVICE, + }, + }; + HRESULT hr; + + hr = IDirectInputDevice8_GetProperty( device, DIPROP_JOYSTICKID, &prop_dword.diph ); + ok_(__FILE__, line)( hr == S_OK, "Unexpected hr %#lx.\n", hr ); + todo_wine_if(todo) ok_(__FILE__, line)( prop_dword.dwData == joystick_id, + "Unexpected joystick id %#lx.\n", prop_dword.dwData ); +} + +static void test_joystick_id( DWORD version ) +{ +#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 descs[4] = + { +#define MAKE_ATTR( pid) { sizeof(HID_DEVICE_ATTRIBUTES), LOWORD(EXPECT_VIDPID), pid, 0x0100 } +#define MAKE_DESC( pid, serial ) {.use_report_id = TRUE, .caps = {.InputReportByteLength = 1}, .attributes = MAKE_ATTR( pid ), .serial_str = serial} + MAKE_DESC( 0x0001, L"2&2345&6" ), + MAKE_DESC( 0x0001, L"3&7812&1" ), + MAKE_DESC( 0x0001, L"4&4580&7" ), + MAKE_DESC( 0x0002, L"5&5880&3" ), + }; + struct hid_device_desc descs_bulk[18] = + { + MAKE_DESC( 0x0003, L"0&2345&0" ), + MAKE_DESC( 0x0004, L"1&7812&0" ), + MAKE_DESC( 0x0005, L"2&4580&0" ), + MAKE_DESC( 0x0006, L"3&5880&0" ), + MAKE_DESC( 0x0007, L"4&2345&0" ), + MAKE_DESC( 0x0008, L"5&7812&0" ), + MAKE_DESC( 0x0009, L"6&4580&0" ), + MAKE_DESC( 0x000a, L"7&5880&0" ), + MAKE_DESC( 0x000b, L"8&2345&0" ), + MAKE_DESC( 0x000c, L"9&7812&0" ), + MAKE_DESC( 0x000d, L"a&4580&0" ), + MAKE_DESC( 0x000e, L"b&5880&0" ), + MAKE_DESC( 0x000f, L"c&2345&0" ), + MAKE_DESC( 0x0010, L"d&7812&0" ), + MAKE_DESC( 0x0011, L"e&4580&0" ), + MAKE_DESC( 0x0012, L"f&5880&0" ), + MAKE_DESC( 0x0013, L"f&6880&0" ), + MAKE_DESC( 0x0014, L"f&7880&0" ), +#undef MAKE_DESC +#undef MAKE_ATTR + }; + struct dinput di = {.version = version}; + GUID instances[64], *instances_end; + IDirectInputDevice8W *device; + HRESULT hr; + ULONG ref; + + winetest_push_context( "%#lx", version ); + cleanup_registry_keys(); + + if (FAILED(hr = dinput_create( &di ))) + { + win_skip( "DirectInput8Create returned %#lx\n", hr ); + winetest_pop_context(); + return; + } + + for (UINT i = 0; i < ARRAY_SIZE(descs); i++) + { + descs[i].report_descriptor_len = sizeof(report_desc); + memcpy( descs[i].report_descriptor_buf, report_desc, sizeof(report_desc) ); + } + + if (!hid_device_start( descs, ARRAY_SIZE(descs) )) goto done; + + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 4, "Unexpected count %Iu.\n", instances_end - instances ); + + /* + * Enumerate all devices, joystick ID is initially assigned based on + * enumeration order. + */ + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[i].serial_str ); + check_device_joystick_id( device, i, TRUE ); + + IDirectInputDevice8_Release( device ); + winetest_pop_context(); + } + + /* + * Joystick ID is associated with a particular guidInstance once created. + * If we stop device 2, device 3 will still have Joystick ID 3, even if + * that no longer matches enumeration order. + */ + hid_device_stop( &descs[2], 1 ); + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 3, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + const unsigned int expected_dev_idx = (i >= 2) ? i + 1 : i; + + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[expected_dev_idx].serial_str ); + check_device_joystick_id( device, expected_dev_idx, TRUE ); + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Delete the existing registry keys, this will result in a new set of + * guidInstance values (and joystick IDs) being generated on the next call to + * EnumDevices(). + */ + cleanup_registry_keys(); + + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 3, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + const unsigned int expected_dev_idx = (i >= 2) ? i + 1 : i; + + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[expected_dev_idx].serial_str ); + check_device_joystick_id( device, i, TRUE ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Restart device 2. Due to enumeration order this should in theory get + * joystick ID 2, but because this was already taken by device 3, it will + * get joystick ID 3 instead. + */ + hid_device_start( &descs[2], 1 ); + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 4, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[i].serial_str ); + if (i < 2) + check_device_joystick_id( device, i, TRUE ); + else + check_device_joystick_id( device, (i == 2) ? 3 : 2, TRUE ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* Reset things back to normal. */ + cleanup_registry_keys(); + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 4, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[i].serial_str ); + check_device_joystick_id( device, i, TRUE ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Joystick Id values are pulled from the registry by EnumDevices(). + * Set device 0 to have joystick ID 15, which is the highest possible + * value. + */ + if (!set_joystick_id_for_guid_instance(&instances[0], 15)) + trace("Failed to set joystick id for device instance.\n"); + + /* No call to EnumDevices(), no joystick ID change. */ + hr = dinput_create_device( &di, &instances[0], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[0].serial_str ); + check_device_joystick_id( device, 0, TRUE ); + + IDirectInputDevice8_Release(device); + + /* Post EnumDevices() call, now device 0 has joystick ID 15. */ + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 4, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[i].serial_str ); + check_device_joystick_id( device, !i ? 15 : i, TRUE ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Attempting to set the Joystick Id value to anything above 15 results in it being + * reset to 0. + */ + if (!set_joystick_id_for_guid_instance(&instances[0], 18)) + trace("Failed to set joystick id for device instance.\n"); + + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 4, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[i].serial_str ); + check_device_joystick_id( device, i, TRUE ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + /* + * Set device 0 to have joystick ID 1. This will conflict with device 1, + * and as a result device 1 gets Joystick ID 0. + */ + if (!set_joystick_id_for_guid_instance(&instances[0], 1)) + trace("Failed to set joystick id for device instance.\n"); + + /* Post EnumDevices() call, now device 0 has joystick ID 1. */ + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + 4, "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs[i].serial_str ); + if (i < 2) + check_device_joystick_id( device, !i ? 1 : 0, TRUE ); + else + check_device_joystick_id( device, i, TRUE ); + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + hid_device_stop( descs, ARRAY_SIZE(descs) ); + + for (UINT i = 0; i < ARRAY_SIZE(descs_bulk); i++) + { + descs_bulk[i].report_descriptor_len = sizeof(report_desc); + memcpy( descs_bulk[i].report_descriptor_buf, report_desc, sizeof(report_desc) ); + } + + if (!hid_device_start( descs_bulk, ARRAY_SIZE(descs_bulk) )) goto done; + instances_end = instances; + hr = dinput_enum_devices( &di, find_test_device_instances, &instances_end ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + ok( instances_end == instances + ARRAY_SIZE(descs_bulk), "Unexpected count %Iu.\n", instances_end - instances ); + for (UINT i = 0; i < instances_end - instances; i++) + { + winetest_push_context( "device %d", i ); + + hr = dinput_create_device( &di, &instances[i], &device ); + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs_bulk[i].serial_str ); + /* + * Devices 16 and above all get a fixed joystick ID of 17. + */ + if (i >= 16) + check_device_joystick_id( device, 17, TRUE ); + else + check_device_joystick_id( device, i, TRUE ); + + IDirectInputDevice8_Release(device); + winetest_pop_context(); + } + + hid_device_stop( descs_bulk, ARRAY_SIZE(descs_bulk) ); +done: + ref = dinput_release( &di ); + ok( ref == 0, "Release returned %ld\n", ref ); + cleanup_registry_keys(); + winetest_pop_context(); +} + START_TEST( joystick8 ) { char **argv; @@ -6500,6 +6853,10 @@ START_TEST( joystick8 ) test_joystick_instance_guid( 0x700 ); test_joystick_instance_guid( 0x800 ); + test_joystick_id( 0x500 ); + test_joystick_id( 0x700 ); + test_joystick_id( 0x800 ); + test_many_axes_joystick(); test_driving_wheel_axes(); test_rawinput( argv ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10755
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 57 +++++++++++++++++++++++++++-------- dlls/dinput/tests/joystick8.c | 27 ++++++++--------- 2 files changed, 58 insertions(+), 26 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index fa22d806437..6feaa842047 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -96,11 +96,27 @@ static CRITICAL_SECTION joystick_cache_cs = { &joystick_cache_cs_debug, -1, 0, 0 static struct list joystick_cache = LIST_INIT( joystick_cache ); +#define JOY_ID_EMPTY 17u +#define JOY_ID_UNASSIGNED ~0u +#define JOY_ID_MASK_INIT 0xffffu +static DWORD joy_ids = JOY_ID_MASK_INIT; +static BOOL is_joy_id_available( DWORD joy_id ) +{ + return (joy_id < 16) && !!(joy_ids & (1 << joy_id)); +} + +static void get_next_available_joy_id( DWORD *joy_id ) +{ + if (BitScanForward( joy_id, joy_ids )) joy_ids &= ~(1 << *joy_id); + else *joy_id = JOY_ID_EMPTY; +} + struct cache_entry { struct list entry; DIDEVICEINSTANCEW instance; WCHAR path[MAX_PATH]; + DWORD joy_id; }; static const char *debugstr_device_instance( const DIDEVICEINSTANCEW *instance ) @@ -109,12 +125,14 @@ static const char *debugstr_device_instance( const DIDEVICEINSTANCEW *instance ) HIWORD(instance->guidProduct.Data1), debugstr_guid( &instance->guidInstance ) ); } -static HRESULT cache_entry_create( const DIDEVICEINSTANCEW *instance, const WCHAR *path, struct cache_entry **out ) +static HRESULT cache_entry_create( const DIDEVICEINSTANCEW *instance, const WCHAR *path, DWORD joy_id, + struct cache_entry **out ) { struct cache_entry *entry; if (!(entry = calloc( 1, sizeof(*entry) ))) return E_OUTOFMEMORY; entry->instance = *instance; + entry->joy_id = joy_id; wcscpy( entry->path, path ); CharLowerW( entry->path ); @@ -122,7 +140,7 @@ static HRESULT cache_entry_create( const DIDEVICEINSTANCEW *instance, const WCHA return S_OK; } -static HRESULT insert_cache_entry( DIDEVICEINSTANCEW *instance, const WCHAR *path ) +static HRESULT insert_cache_entry( DIDEVICEINSTANCEW *instance, DWORD joy_id, const WCHAR *path ) { struct cache_entry *entry, *next; HRESULT hr; @@ -137,13 +155,15 @@ static HRESULT insert_cache_entry( DIDEVICEINSTANCEW *instance, const WCHAR *pat instance->guidInstance = next->instance.guidInstance; TRACE( "Reusing instance %s, path %s\n", debugstr_device_instance( instance ), debugstr_w( path ) ); next->instance = *instance; + if (!is_joy_id_available( next->joy_id )) next->joy_id = JOY_ID_UNASSIGNED; + else joy_ids &= ~(1 << next->joy_id); wcscpy( next->path, path ); return S_OK; } } #undef SWAP - if (FAILED(hr = cache_entry_create( instance, path, &entry ))) return hr; + if (FAILED(hr = cache_entry_create( instance, path, joy_id, &entry ))) return hr; TRACE( "Created instance %s, path %s\n", debugstr_device_instance( instance ), debugstr_w( path ) ); list_add_before( &next->entry, &entry->entry ); @@ -167,6 +187,13 @@ static void save_registry_instances( HKEY root ) LOWORD(vidpid), HIWORD(vidpid), index ); RegSetKeyValueW( root, buffer, L"GUID", REG_BINARY, (BYTE *)&entry->instance.guidInstance, sizeof(entry->instance.guidInstance) ); + if (wcslen( entry->path )) + { + if (entry->joy_id == JOY_ID_UNASSIGNED) get_next_available_joy_id( &entry->joy_id ); + if (entry->joy_id == JOY_ID_EMPTY) RegDeleteKeyValueW( root, buffer, L"Joystick Id" ); + else RegSetKeyValueW( root, buffer, L"Joystick Id", REG_BINARY, (BYTE *)&entry->joy_id, + sizeof(entry->joy_id) ); + } TRACE( "Saved %04x:%04x index %lu, guid %s\n", LOWORD(vidpid), HIWORD(vidpid), index, debugstr_guid( &entry->instance.guidInstance ) ); @@ -180,14 +207,16 @@ static void load_registry_product_instances( HKEY root, DWORD vidpid ) for (DWORD i = 0; !RegEnumKeyW( root, i, name, ARRAY_SIZE(name) ); i++) { DIDEVICEINSTANCEW instance = {.guidProduct = dinput_pidvid_guid }; - DWORD len = sizeof(GUID); + DWORD len = sizeof(GUID), joy_id; instance.guidProduct.Data1 = vidpid; if (RegGetValueW( root, name, L"GUID", RRF_RT_REG_BINARY, NULL, &instance.guidInstance, &len )) continue; - insert_cache_entry( &instance, L"" ); + len = sizeof(joy_id); + if (RegGetValueW( root, name, L"Joystick Id", RRF_RT_REG_BINARY, NULL, &joy_id, &len )) joy_id = JOY_ID_UNASSIGNED; + insert_cache_entry( &instance, joy_id, L"" ); - TRACE( "Loaded %04x:%04x index %s, guid %s\n", LOWORD(vidpid), HIWORD(vidpid), debugstr_w( name ), - debugstr_guid( &instance.guidInstance ) ); + TRACE( "Loaded %04x:%04x index %s, guid %s, joy_id %#lx\n", LOWORD(vidpid), HIWORD(vidpid), debugstr_w( name ), + debugstr_guid( &instance.guidInstance ), joy_id ); } } @@ -211,7 +240,7 @@ static void load_registry_instances( HKEY root ) } } -static HRESULT get_instance_from_guid( const GUID *guid, DIDEVICEINSTANCEW *instance, WCHAR *path ) +static HRESULT get_instance_from_guid( const GUID *guid, DIDEVICEINSTANCEW *instance, DWORD *joy_id, WCHAR *path ) { struct cache_entry *entry; HRESULT hr = DI_OK; @@ -235,6 +264,7 @@ static HRESULT get_instance_from_guid( const GUID *guid, DIDEVICEINSTANCEW *inst { *instance = entry->instance; wcscpy( path, entry->path ); + *joy_id = entry->joy_id; } LeaveCriticalSection( &joystick_cache_cs ); @@ -247,6 +277,7 @@ void hid_joystick_cleanup_devices(void) { struct list *ptr; + joy_ids = JOY_ID_MASK_INIT; while ((ptr = list_head( &joystick_cache ))) { struct cache_entry *entry = LIST_ENTRY( ptr, struct cache_entry, entry ); @@ -382,6 +413,7 @@ struct hid_joystick WCHAR device_path[MAX_PATH]; HIDD_ATTRIBUTES attrs; HIDP_CAPS caps; + DWORD joy_id; char *input_report_buf; char *output_report_buf; @@ -1007,7 +1039,7 @@ static HRESULT hid_joystick_get_property( IDirectInputDevice8W *iface, DWORD pro case (DWORD_PTR)DIPROP_JOYSTICKID: { DIPROPDWORD *value = (DIPROPDWORD *)header; - value->dwData = impl->base.instance.guidInstance.Data3; + value->dwData = impl->joy_id; return DI_OK; } case (DWORD_PTR)DIPROP_GUIDANDPATH: @@ -1851,7 +1883,7 @@ HRESULT hid_joystick_refresh_devices(void) HIDP_CAPS caps; if (FAILED(hid_joystick_device_try_open( path, &device, &preparsed, &attrs, &caps, &instance, 0x0800 ))) continue; - hr = insert_cache_entry( &instance, path ); + hr = insert_cache_entry( &instance, JOY_ID_UNASSIGNED, path ); HidD_FreePreparsedData( preparsed ); CloseHandle( device ); } @@ -2244,18 +2276,19 @@ HRESULT hid_joystick_create_device( struct dinput *dinput, const GUID *guid, IDi struct hid_joystick *impl = NULL; WCHAR device_path[MAX_PATH]; USAGE_AND_PAGE *usages; + DWORD size, joy_id = 0; char *buffer; HRESULT hr; - DWORD size; TRACE( "dinput %p, guid %s, out %p\n", dinput, debugstr_guid( guid ), out ); *out = NULL; - if (FAILED(hr = get_instance_from_guid( guid, &instance, device_path ))) return hr; + if (FAILED(hr = get_instance_from_guid( guid, &instance, &joy_id, device_path ))) return hr; if (!(impl = calloc( 1, sizeof(*impl) ))) return E_OUTOFMEMORY; dinput_device_init( &impl->base, &hid_joystick_vtbl, guid, dinput ); + impl->joy_id = joy_id; 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 ); diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 96109776e52..97ac8c6003d 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -2107,7 +2107,6 @@ static void test_simple_joystick( DWORD version ) prop_dword.dwData = 0xdeadbeef; hr = IDirectInputDevice8_GetProperty( device, DIPROP_JOYSTICKID, &prop_dword.diph ); ok( hr == DI_OK, "GetProperty DIPROP_JOYSTICKID returned %#lx\n", hr ); - todo_wine ok( prop_dword.dwData == 0, "got %#lx expected 0\n", prop_dword.dwData ); prop_dword.dwData = 0xdeadbeef; @@ -6587,7 +6586,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[i].serial_str ); - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release( device ); winetest_pop_context(); @@ -6613,7 +6612,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[expected_dev_idx].serial_str ); - check_device_joystick_id( device, expected_dev_idx, TRUE ); + check_device_joystick_id( device, expected_dev_idx, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); } @@ -6639,7 +6638,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[expected_dev_idx].serial_str ); - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); @@ -6664,9 +6663,9 @@ static void test_joystick_id( DWORD version ) check_device_hid_serial( device, descs[i].serial_str ); if (i < 2) - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); else - check_device_joystick_id( device, (i == 2) ? 3 : 2, TRUE ); + check_device_joystick_id( device, (i == 2) ? 3 : 2, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); @@ -6686,7 +6685,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[i].serial_str ); - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); @@ -6705,7 +6704,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[0].serial_str ); - check_device_joystick_id( device, 0, TRUE ); + check_device_joystick_id( device, 0, FALSE ); IDirectInputDevice8_Release(device); @@ -6722,7 +6721,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[i].serial_str ); - check_device_joystick_id( device, !i ? 15 : i, TRUE ); + check_device_joystick_id( device, !i ? 15 : i, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); @@ -6747,7 +6746,7 @@ static void test_joystick_id( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); check_device_hid_serial( device, descs[i].serial_str ); - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); @@ -6774,9 +6773,9 @@ static void test_joystick_id( DWORD version ) check_device_hid_serial( device, descs[i].serial_str ); if (i < 2) - check_device_joystick_id( device, !i ? 1 : 0, TRUE ); + check_device_joystick_id( device, !i ? 1 : 0, FALSE ); else - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); } @@ -6806,9 +6805,9 @@ static void test_joystick_id( DWORD version ) * Devices 16 and above all get a fixed joystick ID of 17. */ if (i >= 16) - check_device_joystick_id( device, 17, TRUE ); + check_device_joystick_id( device, 17, FALSE ); else - check_device_joystick_id( device, i, TRUE ); + check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release(device); winetest_pop_context(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10755
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/tests/joystick8.c | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 97ac8c6003d..d52a53792d6 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6810,6 +6810,29 @@ static void test_joystick_id( DWORD version ) check_device_joystick_id( device, i, FALSE ); IDirectInputDevice8_Release(device); + + /* + * Undocumented behavior, guessed at by seeing GUID_SysMouseEm/GUID_SysMouseEm2 + * where their Data1 values are 0x6f1d2b80/0x6f1d2b81 respectively. + * GUID_Joystick has a Data1 value of 0x6f1d2b70, which gives us 16 + * possible values before hitting GUID_SysMouseEm. This aligns with + * the maximum possible unique joystick ID being 15. + */ + if (i <= 15) + { + GUID joystick_id_guid = GUID_Joystick; + + joystick_id_guid.Data1 += i; + hr = dinput_create_device( &di, &joystick_id_guid, &device ); + todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + if (device) + { + check_device_hid_serial( device, descs_bulk[i].serial_str ); + check_device_joystick_id( device, i, TRUE ); + IDirectInputDevice8_Release(device); + } + } + winetest_pop_context(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10755
From: Connor McAdams <cmcadams@codeweavers.com> Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 4 ++++ dlls/dinput/tests/joystick8.c | 13 ++++++------- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 6feaa842047..bc87be39293 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -255,7 +255,11 @@ static HRESULT get_instance_from_guid( const GUID *guid, DIDEVICEINSTANCEW *inst LIST_FOR_EACH_ENTRY( entry, &joystick_cache, struct cache_entry, entry ) { + GUID guid_joystick = GUID_Joystick; + if (!*entry->path) continue; + guid_joystick.Data1 |= entry->joy_id; + if (entry->joy_id < 16 && IsEqualGUID( &guid_joystick, guid )) break; if (IsEqualGUID( &entry->instance.guidProduct, guid )) break; if (IsEqualGUID( &entry->instance.guidInstance, guid )) break; } diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index d52a53792d6..48a7e6ebc0d 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6824,13 +6824,12 @@ static void test_joystick_id( DWORD version ) joystick_id_guid.Data1 += i; hr = dinput_create_device( &di, &joystick_id_guid, &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); - if (device) - { - check_device_hid_serial( device, descs_bulk[i].serial_str ); - check_device_joystick_id( device, i, TRUE ); - IDirectInputDevice8_Release(device); - } + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + + check_device_hid_serial( device, descs_bulk[i].serial_str ); + check_device_joystick_id( device, i, FALSE ); + + IDirectInputDevice8_Release(device); } winetest_pop_context(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10755
On Mon Apr 27 16:34:23 2026 +0000, Rémi Bernon wrote:
Instead of an integer joy_id, we could keep a full GUID guid_joystick, and compare it like the other here. I've changed this now in the current revision, hopefully it's cleaner now. :)
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755#note_137998
On Mon Apr 27 16:34:24 2026 +0000, Rémi Bernon wrote:
Seems weird to do that here. Would be nicer to do that in `insert_cache_entry`/`cache_entry_create`? It does seem odd but I can't really come up with a better place to do it. Basically, what needs to happen is:
- We load all preexisting `Joystick Id` values from the registry, marking the ones that are in use. - Now that we know which are in use, we can assign any available joystick IDs to devices that don't have one yet. If we try to do this in `insert_cache_entry` or `cache_entry_create`, it's possible we assign a joystick ID that conflicts with one that's already in the registry. So we need to first enumerate all devices, and then assign afterwards. This seemed like the best place to do that. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755#note_137999
On Mon Apr 27 16:34:24 2026 +0000, Rémi Bernon wrote:
Is this because 0 is available, or can multiple joystick have ID 0? 0 because it's available. I can add a comment to this test to further clarify that if it'd be useful :)
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755#note_138000
On Mon Apr 27 16:34:24 2026 +0000, Rémi Bernon wrote:
It looks misleading to update the available IDs when the function name seem to only indicate a check. I thought the same thing, thought I couldn't come up with a better name for the function... I've changed the function to only check for availability, and split the update part to be outside of the function.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755#note_138003
On Tue Apr 28 17:12:13 2026 +0000, Connor McAdams wrote:
0 because it's available. I can add a comment to this test to further clarify that if it'd be useful :) I mean, what happens if there's not any slot available?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755#note_138025
On Tue Apr 28 17:12:13 2026 +0000, Rémi Bernon wrote:
I mean, what happens if there's not any slot available? Nevermind there's a test for that.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755#note_138026
This merge request was closed by Rémi Bernon. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10755
participants (3)
-
Connor McAdams -
Connor McAdams (@cmcadams) -
Rémi Bernon (@rbernon)