From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 217 ++++++++++++++++++++++++++++++++++ dlls/dinput/tests/hotplug.c | 2 +- dlls/dinput/tests/joystick8.c | 5 +- 3 files changed, 220 insertions(+), 4 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 64639d3cc6a..2d75194117e 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -62,6 +62,7 @@ static CRITICAL_SECTION_DEBUG joystick_cache_cs_debug = 0, 0, { (DWORD_PTR)(__FILE__ ": joystick_cache_cs") } }; static CRITICAL_SECTION joystick_cache_cs = { &joystick_cache_cs_debug, -1, 0, 0, 0, 0 }; +static HANDLE dinput_reg_mutex; /* Need to enter the CS to access this list. */ static struct list joystick_cache = LIST_INIT(joystick_cache); @@ -118,9 +119,143 @@ static struct joystick_instance *joystick_cache_get_instance_from_guid( const GU void hid_joystick_cleanup( void ) { + if (dinput_reg_mutex) CloseHandle( dinput_reg_mutex ); joystick_cache_free(); } +struct joystick_instance_id +{ + struct list entry; + + unsigned int idx; + BOOL assigned; + WORD vid, pid; + GUID guid; +}; + +static void joystick_instance_id_list_free( struct list *list ) +{ + struct joystick_instance_id *cur, *cur2; + + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, list, struct joystick_instance_id, entry ) + { + list_remove( &cur->entry ); + free( cur ); + } +} + +static HRESULT add_joystick_instance_id_to_list( WORD vid, WORD pid, unsigned int idx, const GUID *guid, + struct list *list, struct joystick_instance_id **out_id ) +{ + struct joystick_instance_id *instance_id = calloc( 1, sizeof(*instance_id) ); + + if (!instance_id) return E_OUTOFMEMORY; + + instance_id->vid = vid; + instance_id->pid = pid; + instance_id->idx = idx; + instance_id->guid = *guid; + list_add_tail( list, &instance_id->entry ); + if (out_id) *out_id = instance_id; + return DI_OK; +} + +/* Joystick instance ID registry access functions. */ +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 ); +} + +static const WCHAR *dinput_path = L"System\\CurrentControlSet\\Control\\MediaProperties\\" + "PrivateProperties\\DirectInput"; +static void set_joystick_instance_id_in_registry( struct joystick_instance_id *inst_id ) +{ + WCHAR buf[MAX_PATH]; + LSTATUS ret; + HKEY hkey; + + swprintf( buf, ARRAY_SIZE(buf), L"%s\\VID_%04X&PID_%04X\\Calibration\\%d", dinput_path, inst_id->vid, inst_id->pid, + inst_id->idx ); + if (RegCreateKeyExW( HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &hkey, NULL )) + { + ERR( "Failed to create key.\n" ); + return; + } + + if ((ret = RegSetValueExW( hkey, L"GUID", 0, REG_BINARY, (const BYTE *)&inst_id->guid, + sizeof(inst_id->guid) ))) + WARN( "Failed to set instance GUID value in registry with error %#lx.\n", ret ); + + RegCloseKey( hkey ); +} + +/* + * Read the current joystick instances from the registry and return the + * values in a list. + */ +static HRESULT hid_joystick_get_instance_ids( struct list *out_list ) +{ + WCHAR buf[MAX_PATH]; + HRESULT hr = S_OK; + HKEY root_key; + + if (RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) return hr; + + for (UINT i = 0; !RegEnumKeyW( root_key, i, buf, ARRAY_SIZE(buf) ); i++) + { + WORD vid, pid; + HKEY dev_key; + + 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 (UINT j = 0; !RegEnumKeyW( dev_key, j, buf, ARRAY_SIZE(buf) ); j++) + { + unsigned int idx; + DWORD len; + GUID guid; + + if (swscanf( buf, L"%d", &idx ) != 1) continue; + + len = sizeof(guid); + if (RegGetValueW( dev_key, buf, L"GUID", RRF_RT_REG_BINARY, NULL, &guid, &len )) + { + WARN( "Failed to get GUID key from %s in registry, expect problems.\n", debugstr_w( buf ) ); + continue; + } + + hr = add_joystick_instance_id_to_list( vid, pid, idx, &guid, out_list, NULL ); + if (FAILED(hr)) break; + } + RegCloseKey( dev_key ); + if (FAILED(hr)) break; + } + + if (FAILED(hr)) joystick_instance_id_list_free( out_list ); + RegCloseKey( root_key ); + return hr; +} + struct pid_control_report { BYTE id; @@ -1705,14 +1840,57 @@ exit: return ret_val; } +static HRESULT get_joystick_instance_id_for_joystick( WORD vid, WORD pid, struct list *cur_ids, + struct list *new_ids, struct joystick_instance_id **out_id ) +{ + struct joystick_instance_id *instance_id_prev, *tmp; + + /* First, try to find an unassigned ID from the existing registry entries. */ + instance_id_prev = tmp = NULL; + LIST_FOR_EACH_ENTRY( tmp, cur_ids, struct joystick_instance_id, entry ) + { + if (tmp->vid == vid && tmp->pid == pid) + { + if (!tmp->assigned) + { + *out_id = tmp; + return DI_OK; + } + instance_id_prev = tmp; + } + } + + /* + * There were no unassigned entries in the current registry list, need to + * check if we've added any new entries for this vid/pid pair already to + * get the proper index. + */ + LIST_FOR_EACH_ENTRY( tmp, new_ids, struct joystick_instance_id, entry ) + { + if (tmp->vid == vid && tmp->pid == pid) + { + if (instance_id_prev) assert( tmp->idx > instance_id_prev->idx ); + instance_id_prev = tmp; + } + } + + return add_joystick_instance_id_to_list( vid, pid, instance_id_prev ? instance_id_prev->idx + 1 : 0, + &GUID_NULL, new_ids, out_id ); +} + HRESULT hid_joystick_refresh_devices( void ) { + struct list cur_instance_ids, new_instance_ids; + struct joystick_instance_id *cur_id; + struct joystick_instance *cur; WCHAR *hid_list, *tmp; HRESULT hr; GUID hid; TRACE( "\n" ); + list_init( &cur_instance_ids ); + list_init( &new_instance_ids ); hid_list = NULL; HidD_GetHidGuid( &hid ); hr = hid_joystick_get_class_dev_iface_list( &hid, NULL, &hid_list ); @@ -1750,7 +1928,46 @@ HRESULT hid_joystick_refresh_devices( void ) list_add_tail( &joystick_cache, &instance->entry ); } + /* + * Pull all guidInstance values that currently exist in the registry, and + * store them in cur_instance_ids. + */ + get_dinput_reg_mutex(); + hr = hid_joystick_get_instance_ids( &cur_instance_ids ); + if (FAILED(hr)) + { + release_dinput_reg_mutex(); + goto exit; + } + + /* + * Now, go through our list of joystick devices and assign them IDs from the + * instance ID list. If we run out of IDs, generate new ones. + */ + LIST_FOR_EACH_ENTRY( cur, &joystick_cache, struct joystick_instance, entry ) + { + struct joystick_instance_id *id; + + hr = get_joystick_instance_id_for_joystick( cur->vid, cur->pid, &cur_instance_ids, &new_instance_ids, &id ); + if (FAILED(hr)) + { + release_dinput_reg_mutex(); + goto exit; + } + + id->assigned = TRUE; + if (IsEqualGUID( &id->guid, &GUID_NULL )) id->guid = cur->guid; + cur->guid = id->guid; + } + + /* New IDs to write into the registry. */ + LIST_FOR_EACH_ENTRY( cur_id, &new_instance_ids, struct joystick_instance_id, entry ) + set_joystick_instance_id_in_registry(cur_id); + release_dinput_reg_mutex(); + exit: + joystick_instance_id_list_free( &cur_instance_ids ); + joystick_instance_id_list_free( &new_instance_ids ); if (FAILED(hr)) joystick_cache_free(); free( hid_list ); LeaveCriticalSection( &joystick_cache_cs ); diff --git a/dlls/dinput/tests/hotplug.c b/dlls/dinput/tests/hotplug.c index 10a3051e8ce..e4147797652 100644 --- a/dlls/dinput/tests/hotplug.c +++ b/dlls/dinput/tests/hotplug.c @@ -222,7 +222,7 @@ static BOOL test_input_lost( DWORD version ) hr = dinput_test_create_device( version, &devinst2, &device2 ); ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); ok( !!device2, "device2 is NULL.\n" ); - todo_wine ok( !memcmp( &devinst.guidInstance, &devinst2.guidInstance, sizeof(GUID) ), + ok( !memcmp( &devinst.guidInstance, &devinst2.guidInstance, sizeof(GUID) ), "Unexpected guidInstance.\n" ); ref = IDirectInputDevice8_Release( device2 ); ok( ref == 0, "Release returned %ld\n", ref ); diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index cafc43d8b72..2c395cb4462 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6311,7 +6311,7 @@ static void test_joystick_instance_guid( DWORD version ) winetest_push_context( "device %d", i ); - todo_wine ok( IsEqualGUID( &expect_instances[expected_joystick - 1], &instances[i] ), + ok( IsEqualGUID( &expect_instances[expected_joystick - 1], &instances[i] ), "Unexpected instance %s.\n", debugstr_guid( &instances[i] ) ); hr = dinput_create_device( &di, &instances[i], &device ); ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); @@ -6335,7 +6335,7 @@ static void test_joystick_instance_guid( DWORD version ) winetest_push_context( "device %d", i ); - todo_wine ok( IsEqualGUID( &expect_instances[expected_joystick - 1], &instances[i] ), + ok( IsEqualGUID( &expect_instances[expected_joystick - 1], &instances[i] ), "Unexpected instance %s.\n", debugstr_guid( &instances[i] ) ); hr = dinput_create_device( &di, &instances[i], &device ); ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); @@ -6356,7 +6356,6 @@ static void test_joystick_instance_guid( DWORD version ) { winetest_push_context( "device %d", i ); - todo_wine_if( !(i & 0x1) ) ok( IsEqualGUID( &expect_instances[i], &instances[i] ), "Unexpected guidInstance %s.\n", debugstr_guid( &instances[i] ) ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364