[PATCH v5 0/8] 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. -- v5: dinput: Refresh joystick devices when an unknown GUID is used to create a device. dinput: Return E_FAIL when opening a joystick device that is currently disconnected. dinput: Create a version 1 UUID for dinput joystick devices. dinput: Create registry entries for HID joysticks. dinput: Use joystick cache in hid_joystick_create_device(). dinput: Use joystick cache in hid_joystick_enum_device(). dinput: Create a cache of currently connected HID joysticks. dinput: Only pass instance name string to device_instance_is_disabled(). 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/device.c | 16 +++++++--------- dlls/dinput/device_private.h | 2 +- dlls/dinput/joystick_hid.c | 2 +- 3 files changed, 9 insertions(+), 11 deletions(-) diff --git a/dlls/dinput/device.c b/dlls/dinput/device.c index 35518dd91dd..142a9908b29 100644 --- a/dlls/dinput/device.c +++ b/dlls/dinput/device.c @@ -145,11 +145,9 @@ DWORD get_config_key( HKEY defkey, HKEY appkey, const WCHAR *name, WCHAR *buffer return ERROR_FILE_NOT_FOUND; } -BOOL device_instance_is_disabled( DIDEVICEINSTANCEW *instance, BOOL *override ) +BOOL device_instance_is_disabled( const WCHAR *instance, BOOL *override ) { - static const WCHAR disabled_str[] = {'d', 'i', 's', 'a', 'b', 'l', 'e', 'd', 0}; - static const WCHAR override_str[] = {'o', 'v', 'e', 'r', 'r', 'i', 'd', 'e', 0}; - static const WCHAR joystick_key[] = {'J', 'o', 'y', 's', 't', 'i', 'c', 'k', 's', 0}; + static const WCHAR *joystick_key = L"Joysticks"; WCHAR buffer[MAX_PATH]; HKEY hkey, appkey, temp; BOOL disable = FALSE; @@ -173,16 +171,16 @@ BOOL device_instance_is_disabled( DIDEVICEINSTANCEW *instance, BOOL *override ) } /* Look for the "controllername"="disabled" key */ - if (!get_config_key( hkey, appkey, instance->tszInstanceName, buffer, sizeof(buffer) )) + if (!get_config_key( hkey, appkey, instance, buffer, sizeof(buffer) )) { - if (!wcscmp( disabled_str, buffer )) + if (!wcscmp( L"disabled", buffer )) { - TRACE( "Disabling joystick '%s' based on registry key.\n", debugstr_w(instance->tszInstanceName) ); + TRACE( "Disabling joystick '%s' based on registry key.\n", debugstr_w(instance) ); disable = TRUE; } - else if (override && !wcscmp( override_str, buffer )) + else if (override && !wcscmp( L"override", buffer )) { - TRACE( "Force enabling joystick '%s' based on registry key.\n", debugstr_w(instance->tszInstanceName) ); + TRACE( "Force enabling joystick '%s' based on registry key.\n", debugstr_w(instance) ); *override = TRUE; } } diff --git a/dlls/dinput/device_private.h b/dlls/dinput/device_private.h index fa791f08a9d..fcab99779c5 100644 --- a/dlls/dinput/device_private.h +++ b/dlls/dinput/device_private.h @@ -134,7 +134,7 @@ extern BOOL device_object_matches_semantic( const DIDEVICEINSTANCEW *instance, c extern BOOL get_app_key(HKEY*, HKEY*); extern DWORD get_config_key( HKEY, HKEY, const WCHAR *, WCHAR *, DWORD ); -extern BOOL device_instance_is_disabled( DIDEVICEINSTANCEW *instance, BOOL *override ); +extern BOOL device_instance_is_disabled( const WCHAR *instance, BOOL *override ); extern void queue_event( IDirectInputDevice8W *iface, int index, DWORD data, DWORD time, DWORD seq ); extern const GUID dinput_pidvid_guid; diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 22709c4bfd9..c2954553805 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -1605,7 +1605,7 @@ static HRESULT hid_joystick_device_open( int index, const GUID *guid, DIDEVICEIN attrs, caps, instance, version ))) continue; - if (device_instance_is_disabled( instance, &override )) + if (device_instance_is_disabled( instance->tszInstanceName, &override )) goto next; if (override && SetupDiGetDeviceInstanceIdW( set, &devinfo, device_id, MAX_PATH, NULL ) && -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/dinput.c | 1 + dlls/dinput/dinput_main.c | 1 + dlls/dinput/dinput_private.h | 2 + dlls/dinput/joystick_hid.c | 149 ++++++++++++++++++++++++++++++++++- 4 files changed, 152 insertions(+), 1 deletion(-) diff --git a/dlls/dinput/dinput.c b/dlls/dinput/dinput.c index f851e17a244..1695221d3a6 100644 --- a/dlls/dinput/dinput.c +++ b/dlls/dinput/dinput.c @@ -372,6 +372,7 @@ static HRESULT WINAPI dinput8_EnumDevices( IDirectInput8W *iface, DWORD type, LP if (device_class == DI8DEVCLASS_ALL || device_class == DI8DEVCLASS_GAMECTRL) { + if (FAILED(hid_joystick_refresh_devices())) WARN( "Failed to refresh HID joystick devices.\n" ); do { hr = hid_joystick_enum_device( type, flags, &instance, impl->dwVersion, i++ ); diff --git a/dlls/dinput/dinput_main.c b/dlls/dinput/dinput_main.c index e4bc1f7961d..b2dfaf76a92 100644 --- a/dlls/dinput/dinput_main.c +++ b/dlls/dinput/dinput_main.c @@ -523,6 +523,7 @@ BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, void *reserved ) case DLL_PROCESS_DETACH: if (reserved) break; unregister_di_em_win_class(); + hid_joystick_cleanup(); break; } return TRUE; diff --git a/dlls/dinput/dinput_private.h b/dlls/dinput/dinput_private.h index 0609b816b3b..f2d2ec1484c 100644 --- a/dlls/dinput/dinput_private.h +++ b/dlls/dinput/dinput_private.h @@ -56,6 +56,8 @@ 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 HRESULT hid_joystick_refresh_devices( void ); +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 c2954553805..8c6a752c020 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -36,7 +36,7 @@ #include "setupapi.h" #include "devguid.h" #include "dinput.h" -#include "setupapi.h" +#include "cfgmgr32.h" #include "dinput_private.h" #include "device_private.h" @@ -52,6 +52,42 @@ DEFINE_GUID( GUID_DEVINTERFACE_WINEXINPUT,0x6c53d5fd,0x6480,0x440f,0xb6,0x18,0x4 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 ); +static CRITICAL_SECTION joystick_cache_cs; +static CRITICAL_SECTION_DEBUG joystick_cache_cs_debug = +{ + 0, 0, &joystick_cache_cs, + { &joystick_cache_cs_debug.ProcessLocksList, &joystick_cache_cs_debug.ProcessLocksList }, + 0, 0, { (DWORD_PTR)(__FILE__ ": joystick_cache_cs") } +}; +static CRITICAL_SECTION joystick_cache_cs = { &joystick_cache_cs_debug, -1, 0, 0, 0, 0 }; + +/* Need to enter the CS to access this list. */ +static struct list joystick_cache = LIST_INIT(joystick_cache); + +struct joystick_instance +{ + struct list entry; + WORD vid, pid; + + WCHAR dev_path[MAX_PATH]; +}; + +static void joystick_cache_free( void ) +{ + struct joystick_instance *cur, *cur2; + + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &joystick_cache, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + free(cur); + } +} + +void hid_joystick_cleanup( void ) +{ + joystick_cache_free(); +} + struct pid_control_report { BYTE id; @@ -1572,6 +1608,117 @@ failed: return DIERR_DEVICENOTREG; } +static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, WCHAR **list ) +{ + WCHAR *ifaces = NULL; + GUID guid = *class; + ULONG size = 0; + CONFIGRET ret; + + *list = NULL; + while (1) + { + if ((ret = CM_Get_Device_Interface_List_SizeW( &size, &guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT ))) + WARN( "Got unexpected return value %lu.\n", ret ); + if (ret || size == 1) return DIERR_DEVICENOTREG; + if (!(ifaces = calloc( size, sizeof(*ifaces) ))) return E_OUTOFMEMORY; + + if (!(ret = CM_Get_Device_Interface_ListW( &guid, NULL, ifaces, size, CM_GET_DEVICE_INTERFACE_LIST_PRESENT ))) + break; + free( ifaces ); + if (ret != CR_BUFFER_SMALL) return E_FAIL; + } + + *list = ifaces; + return DI_OK; +} + +static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid ) +{ + PHIDP_PREPARSED_DATA preparsed = NULL; + HIDD_ATTRIBUTES attrs = { 0 }; + HANDLE device_file = NULL; + WCHAR product[MAX_PATH]; + BOOL ret_val = FALSE; + HIDP_CAPS caps; + + 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 FALSE; + + if (!HidD_GetPreparsedData( device_file, &preparsed )) goto exit; + if (!HidD_GetAttributes( device_file, &attrs )) goto exit; + if (HidP_GetCaps( preparsed, &caps ) != HIDP_STATUS_SUCCESS) goto exit; + + switch (MAKELONG( caps.Usage, caps.UsagePage )) + { + case MAKELONG( HID_USAGE_GENERIC_GAMEPAD, HID_USAGE_PAGE_GENERIC ): break; + case MAKELONG( HID_USAGE_GENERIC_JOYSTICK, HID_USAGE_PAGE_GENERIC ): break; + case MAKELONG( HID_USAGE_GENERIC_MOUSE, HID_USAGE_PAGE_GENERIC ): goto exit; + case MAKELONG( HID_USAGE_GENERIC_KEYBOARD, HID_USAGE_PAGE_GENERIC ): goto exit; + default: FIXME( "device usage %04x:%04x not implemented!\n", caps.UsagePage, caps.Usage); goto exit; + } + + if (!HidD_GetProductString( device_file, product, sizeof(product) )) goto exit; + if (device_instance_is_disabled( product, NULL )) goto exit; + *vid = attrs.VendorID; + *pid = attrs.ProductID; + ret_val = TRUE; + +exit: + HidD_FreePreparsedData( preparsed ); + CloseHandle( device_file ); + return ret_val; +} + +HRESULT hid_joystick_refresh_devices( void ) +{ + WCHAR *hid_list, *tmp; + HRESULT hr; + GUID hid; + + TRACE( "\n" ); + + hid_list = NULL; + HidD_GetHidGuid( &hid ); + hr = hid_joystick_get_class_dev_iface_list( &hid, &hid_list ); + if (FAILED(hr)) return hr; + + /* + * Iterate over connected HID devices, find all joysticks, and add them to + * the joystick local cache list. + */ + EnterCriticalSection( &joystick_cache_cs ); + joystick_cache_free(); + for (tmp = hid_list; *tmp; tmp = tmp + wcslen( tmp ) + 1) + { + struct joystick_instance *instance = NULL; + WORD vid, pid; + + if (!hid_device_is_valid_joystick( tmp, &vid, &pid )) continue; + if (!(instance = malloc( sizeof(*instance) ))) + { + hr = E_OUTOFMEMORY; + goto exit; + } + instance->vid = vid; + instance->pid = pid; + wcscpy( instance->dev_path, tmp ); + /* + * SetupAPI returns paths in lowercase, cfgmgr32 does not. joy.cpl + * depends on this being lowercase. + */ + CharLowerW( instance->dev_path ); + list_add_tail( &joystick_cache, &instance->entry ); + } + +exit: + if (FAILED(hr)) joystick_cache_free(); + free( hid_list ); + LeaveCriticalSection( &joystick_cache_cs ); + return hr; +} + 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 ) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/Makefile.in | 2 +- dlls/dinput/joystick_hid.c | 80 +++++++++++++++++++++++++++++++++----- dlls/dinput8/Makefile.in | 2 +- 3 files changed, 72 insertions(+), 12 deletions(-) diff --git a/dlls/dinput/Makefile.in b/dlls/dinput/Makefile.in index 70aa575a837..883639195ba 100644 --- a/dlls/dinput/Makefile.in +++ b/dlls/dinput/Makefile.in @@ -1,6 +1,6 @@ MODULE = dinput.dll IMPORTLIB = dinput -IMPORTS = dinput dxguid uuid comctl32 ole32 user32 advapi32 hid setupapi +IMPORTS = dinput dxguid uuid comctl32 ole32 user32 advapi32 hid setupapi cfgmgr32 EXTRADEFS = -DDIRECTINPUT_VERSION=0x0700 VER_FILEDESCRIPTION_STR = "Wine DirectInput" diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 8c6a752c020..b475eb64000 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -42,6 +42,8 @@ #include "device_private.h" #include "initguid.h" +#include "devpkey.h" +#include "propkey.h" #include "wine/debug.h" #include "wine/hid.h" @@ -67,6 +69,7 @@ static struct list joystick_cache = LIST_INIT(joystick_cache); struct joystick_instance { struct list entry; + BOOL override; WORD vid, pid; WCHAR dev_path[MAX_PATH]; @@ -83,6 +86,21 @@ static void joystick_cache_free( void ) } } +static struct joystick_instance *joystick_cache_get_instance_from_index( unsigned int index ) +{ + struct joystick_instance *inst = NULL; + unsigned int i = 0; + + if (index >= list_count( &joystick_cache )) return NULL; + + LIST_FOR_EACH_ENTRY( inst, &joystick_cache, struct joystick_instance, entry ) + { + if (index == i++) break; + } + + return inst; +} + void hid_joystick_cleanup( void ) { joystick_cache_free(); @@ -1608,7 +1626,7 @@ failed: return DIERR_DEVICENOTREG; } -static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, WCHAR **list ) +static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, const DEVINSTID_W instance_id, WCHAR **list ) { WCHAR *ifaces = NULL; GUID guid = *class; @@ -1618,12 +1636,12 @@ static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, WCHAR * *list = NULL; while (1) { - if ((ret = CM_Get_Device_Interface_List_SizeW( &size, &guid, NULL, CM_GET_DEVICE_INTERFACE_LIST_PRESENT ))) + if ((ret = CM_Get_Device_Interface_List_SizeW( &size, &guid, instance_id, CM_GET_DEVICE_INTERFACE_LIST_PRESENT ))) WARN( "Got unexpected return value %lu.\n", ret ); if (ret || size == 1) return DIERR_DEVICENOTREG; if (!(ifaces = calloc( size, sizeof(*ifaces) ))) return E_OUTOFMEMORY; - if (!(ret = CM_Get_Device_Interface_ListW( &guid, NULL, ifaces, size, CM_GET_DEVICE_INTERFACE_LIST_PRESENT ))) + if (!(ret = CM_Get_Device_Interface_ListW( &guid, instance_id, ifaces, size, CM_GET_DEVICE_INTERFACE_LIST_PRESENT ))) break; free( ifaces ); if (ret != CR_BUFFER_SMALL) return E_FAIL; @@ -1633,7 +1651,7 @@ static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, WCHAR * return DI_OK; } -static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid ) +static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid, BOOL *override ) { PHIDP_PREPARSED_DATA preparsed = NULL; HIDD_ATTRIBUTES attrs = { 0 }; @@ -1660,7 +1678,7 @@ static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pi } if (!HidD_GetProductString( device_file, product, sizeof(product) )) goto exit; - if (device_instance_is_disabled( product, NULL )) goto exit; + if (device_instance_is_disabled( product, override )) goto exit; *vid = attrs.VendorID; *pid = attrs.ProductID; ret_val = TRUE; @@ -1681,7 +1699,7 @@ HRESULT hid_joystick_refresh_devices( void ) hid_list = NULL; HidD_GetHidGuid( &hid ); - hr = hid_joystick_get_class_dev_iface_list( &hid, &hid_list ); + hr = hid_joystick_get_class_dev_iface_list( &hid, NULL, &hid_list ); if (FAILED(hr)) return hr; /* @@ -1693,9 +1711,10 @@ HRESULT hid_joystick_refresh_devices( void ) for (tmp = hid_list; *tmp; tmp = tmp + wcslen( tmp ) + 1) { struct joystick_instance *instance = NULL; + BOOL override; WORD vid, pid; - if (!hid_device_is_valid_joystick( tmp, &vid, &pid )) continue; + if (!hid_device_is_valid_joystick( tmp, &vid, &pid, &override )) continue; if (!(instance = malloc( sizeof(*instance) ))) { hr = E_OUTOFMEMORY; @@ -1703,6 +1722,7 @@ HRESULT hid_joystick_refresh_devices( void ) } instance->vid = vid; instance->pid = pid; + instance->override = override; wcscpy( instance->dev_path, tmp ); /* * SetupAPI returns paths in lowercase, cfgmgr32 does not. joy.cpl @@ -1794,20 +1814,60 @@ static HRESULT hid_joystick_device_open( int index, const GUID *guid, DIDEVICEIN return DI_OK; } +static HRESULT hid_joystick_instance_try_open( struct joystick_instance *instance, HANDLE *device, + PHIDP_PREPARSED_DATA *preparsed, HIDD_ATTRIBUTES *attrs, HIDP_CAPS *caps, + DIDEVICEINSTANCEW *di_instance, WCHAR *device_path, DWORD version ) +{ + WCHAR *path = instance->dev_path; + HRESULT hr; + + if (instance->override) + { + WCHAR instance_id[MAX_DEVICE_ID_LEN] = { 0 }; + DEVPROPTYPE type; + CONFIGRET ret; + WCHAR *tmp; + ULONG size; + + size = sizeof(instance_id); + ret = CM_Get_Device_Interface_PropertyW( instance->dev_path, &DEVPKEY_Device_InstanceId, &type, + (BYTE *)instance_id, &size, 0 ); + if (ret || !(tmp = wcsstr( instance_id, L"&IG_" ))) + { + WARN( "Failed to get valid instance id for overridden device.\n" ); + return E_FAIL; + } + + memcpy( tmp, L"&XI_", sizeof(L"&XI_") - sizeof(WCHAR) ); + if (FAILED(hid_joystick_get_class_dev_iface_list( &GUID_DEVINTERFACE_WINEXINPUT, instance_id, &path ))) + return E_FAIL; + } + + hr = hid_joystick_device_try_open( path, device, preparsed, attrs, caps, di_instance, version ); + if (SUCCEEDED(hr)) wcscpy( device_path, path ); + if (path != instance->dev_path) free( path ); + return hr; +} + + 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; + struct joystick_instance *inst; WCHAR device_path[MAX_PATH]; - GUID guid = GUID_NULL; HIDP_CAPS caps; HANDLE device; HRESULT hr; 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 ); + EnterCriticalSection( &joystick_cache_cs ); + if ((inst = joystick_cache_get_instance_from_index( index ))) + hr = hid_joystick_instance_try_open( inst, &device, &preparsed, &attrs, &caps, instance, device_path, version ); + else + hr = DIERR_DEVICENOTREG; + LeaveCriticalSection( &joystick_cache_cs ); if (hr != DI_OK) return hr; HidD_FreePreparsedData( preparsed ); diff --git a/dlls/dinput8/Makefile.in b/dlls/dinput8/Makefile.in index 1b288a1dce2..bdb50f9fa05 100644 --- a/dlls/dinput8/Makefile.in +++ b/dlls/dinput8/Makefile.in @@ -1,6 +1,6 @@ MODULE = dinput8.dll IMPORTLIB = dinput8 -IMPORTS = dinput8 dxguid uuid comctl32 ole32 user32 advapi32 hid setupapi +IMPORTS = dinput8 dxguid uuid comctl32 ole32 user32 advapi32 hid setupapi cfgmgr32 EXTRADEFS = -DDIRECTINPUT_VERSION=0x0800 PARENTSRC = ../dinput -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 136 ++++++++++++---------------------- dlls/dinput/tests/joystick8.c | 2 +- 2 files changed, 47 insertions(+), 91 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index b475eb64000..64639d3cc6a 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -71,6 +71,7 @@ struct joystick_instance struct list entry; BOOL override; WORD vid, pid; + GUID guid; WCHAR dev_path[MAX_PATH]; }; @@ -101,6 +102,20 @@ static struct joystick_instance *joystick_cache_get_instance_from_index( unsigne return inst; } +static struct joystick_instance *joystick_cache_get_instance_from_guid( const GUID *guid ) +{ + GUID guid_product = dinput_pidvid_guid; + struct joystick_instance *inst = NULL; + + LIST_FOR_EACH_ENTRY( inst, &joystick_cache, struct joystick_instance, entry ) + { + guid_product.Data1 = MAKELONG( inst->vid, inst->pid); + if (IsEqualGUID( &inst->guid, guid ) || IsEqualGUID( &guid_product, guid )) return inst; + } + + return NULL; +} + void hid_joystick_cleanup( void ) { joystick_cache_free(); @@ -1475,13 +1490,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, @@ -1504,14 +1518,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; @@ -1651,7 +1658,7 @@ static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, const D return DI_OK; } -static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid, BOOL *override ) +static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid, BOOL *override, GUID *guid_instance ) { PHIDP_PREPARSED_DATA preparsed = NULL; HIDD_ATTRIBUTES attrs = { 0 }; @@ -1659,6 +1666,8 @@ static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pi WCHAR product[MAX_PATH]; BOOL ret_val = FALSE; HIDP_CAPS caps; + UINT32 handle; + DWORD size; 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 ); @@ -1679,6 +1688,13 @@ static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pi if (!HidD_GetProductString( device_file, product, sizeof(product) )) goto exit; if (device_instance_is_disabled( product, override )) goto exit; + 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 exit; + } + *guid_instance = hid_joystick_guid; + guid_instance->Data1 ^= handle; *vid = attrs.VendorID; *pid = attrs.ProductID; ret_val = TRUE; @@ -1713,8 +1729,9 @@ HRESULT hid_joystick_refresh_devices( void ) struct joystick_instance *instance = NULL; BOOL override; WORD vid, pid; + GUID guid; - if (!hid_device_is_valid_joystick( tmp, &vid, &pid, &override )) continue; + if (!hid_device_is_valid_joystick( tmp, &vid, &pid, &override, &guid )) continue; if (!(instance = malloc( sizeof(*instance) ))) { hr = E_OUTOFMEMORY; @@ -1723,6 +1740,7 @@ HRESULT hid_joystick_refresh_devices( void ) instance->vid = vid; instance->pid = pid; instance->override = override; + instance->guid = guid; wcscpy( instance->dev_path, tmp ); /* * SetupAPI returns paths in lowercase, cfgmgr32 does not. joy.cpl @@ -1739,81 +1757,6 @@ exit: return hr; } -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 ) -{ - 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; - HDEVINFO set, xi_set; - BOOL override; - UINT32 i = 0; - GUID hid; - - TRACE( "index %d, guid %s\n", index, debugstr_guid( guid ) ); - - HidD_GetHidGuid( &hid ); - - set = SetupDiGetClassDevsW( &hid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT ); - if (set == INVALID_HANDLE_VALUE) return DIERR_DEVICENOTREG; - xi_set = SetupDiGetClassDevsW( &GUID_DEVINTERFACE_WINEXINPUT, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT ); - - *device = NULL; - *preparsed = NULL; - while (SetupDiEnumDeviceInterfaces( set, NULL, &hid, i++, &iface )) - { - 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 ))) - continue; - - if (device_instance_is_disabled( instance->tszInstanceName, &override )) - goto next; - - if (override && SetupDiGetDeviceInstanceIdW( set, &devinfo, device_id, MAX_PATH, NULL ) && - (tmp = wcsstr( device_id, L"&IG_" ))) - { - memcpy( tmp, L"&XI_", sizeof(L"&XI_") - sizeof(WCHAR) ); - if (!SetupDiOpenDeviceInfoW( xi_set, device_id, NULL, 0, &devinfo )) - goto next; - if (!SetupDiEnumDeviceInterfaces( xi_set, &devinfo, &GUID_DEVINTERFACE_WINEXINPUT, 0, &iface )) - goto next; - 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 ))) - continue; - } - - /* enumerate device by GUID */ - if (IsEqualGUID( guid, &instance->guidProduct ) || IsEqualGUID( guid, &instance->guidInstance )) break; - - /* enumerate all devices */ - if (index >= 0 && !index--) break; - - next: - CloseHandle( *device ); - HidD_FreePreparsedData( *preparsed ); - *device = NULL; - *preparsed = NULL; - } - - if (xi_set != INVALID_HANDLE_VALUE) SetupDiDestroyDeviceInfoList( xi_set ); - SetupDiDestroyDeviceInfoList( set ); - if (!*device || !*preparsed) return DIERR_DEVICENOTREG; - - lstrcpynW( device_path, detail->DevicePath, MAX_PATH ); - return DI_OK; -} - static HRESULT hid_joystick_instance_try_open( struct joystick_instance *instance, HANDLE *device, PHIDP_PREPARSED_DATA *preparsed, HIDD_ATTRIBUTES *attrs, HIDP_CAPS *caps, DIDEVICEINSTANCEW *di_instance, WCHAR *device_path, DWORD version ) @@ -1844,7 +1787,11 @@ static HRESULT hid_joystick_instance_try_open( struct joystick_instance *instanc } hr = hid_joystick_device_try_open( path, device, preparsed, attrs, caps, di_instance, version ); - if (SUCCEEDED(hr)) wcscpy( device_path, path ); + if (SUCCEEDED(hr)) + { + wcscpy( device_path, path ); + di_instance->guidInstance = instance->guid; + } if (path != instance->dev_path) free( path ); return hr; } @@ -2248,8 +2195,17 @@ HRESULT hid_joystick_create_device( struct dinput *dinput, const GUID *guid, IDi 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 ); + { + struct joystick_instance *inst; + + EnterCriticalSection( &joystick_cache_cs ); + if ((inst = joystick_cache_get_instance_from_guid( guid ))) + hr = hid_joystick_instance_try_open( inst, &impl->device, &impl->preparsed, &attrs, &impl->caps, + &impl->base.instance, impl->device_path, dinput->dwVersion ); + else + hr = DIERR_DEVICENOTREG; + LeaveCriticalSection( &joystick_cache_cs ); + } else { wcscpy( impl->device_path, *(const WCHAR **)guid ); diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 5b2ca8fe66d..cafc43d8b72 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6161,7 +6161,7 @@ static void test_joystick_instance_guid( DWORD version ) ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); hr = dinput_create_device( &di2, &expect_instances[0], &device ); - if (version == 0x800) todo_wine ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + if (version == 0x800) ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); else ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
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
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 54 ++++++++++++++++++++++++----------- dlls/dinput/tests/joystick8.c | 4 +-- 2 files changed, 40 insertions(+), 18 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 2d75194117e..5d21e993d10 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -51,9 +51,41 @@ 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 CRITICAL_SECTION joystick_cache_cs; static CRITICAL_SECTION_DEBUG joystick_cache_cs_debug = { @@ -1793,7 +1825,7 @@ static HRESULT hid_joystick_get_class_dev_iface_list( const GUID *class, const D return DI_OK; } -static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid, BOOL *override, GUID *guid_instance ) +static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pid, BOOL *override ) { PHIDP_PREPARSED_DATA preparsed = NULL; HIDD_ATTRIBUTES attrs = { 0 }; @@ -1801,8 +1833,6 @@ static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pi WCHAR product[MAX_PATH]; BOOL ret_val = FALSE; HIDP_CAPS caps; - UINT32 handle; - DWORD size; 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 ); @@ -1823,13 +1853,6 @@ static BOOL hid_device_is_valid_joystick( const WCHAR *path, WORD *vid, WORD *pi if (!HidD_GetProductString( device_file, product, sizeof(product) )) goto exit; if (device_instance_is_disabled( product, override )) goto exit; - 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 exit; - } - *guid_instance = hid_joystick_guid; - guid_instance->Data1 ^= handle; *vid = attrs.VendorID; *pid = attrs.ProductID; ret_val = TRUE; @@ -1844,6 +1867,7 @@ static HRESULT get_joystick_instance_id_for_joystick( WORD vid, WORD pid, struct struct list *new_ids, struct joystick_instance_id **out_id ) { struct joystick_instance_id *instance_id_prev, *tmp; + GUID uuid; /* First, try to find an unassigned ID from the existing registry entries. */ instance_id_prev = tmp = NULL; @@ -1874,8 +1898,9 @@ static HRESULT get_joystick_instance_id_for_joystick( WORD vid, WORD pid, struct } } + create_v1_uuid( &uuid ); return add_joystick_instance_id_to_list( vid, pid, instance_id_prev ? instance_id_prev->idx + 1 : 0, - &GUID_NULL, new_ids, out_id ); + &uuid, new_ids, out_id ); } HRESULT hid_joystick_refresh_devices( void ) @@ -1907,9 +1932,8 @@ HRESULT hid_joystick_refresh_devices( void ) struct joystick_instance *instance = NULL; BOOL override; WORD vid, pid; - GUID guid; - if (!hid_device_is_valid_joystick( tmp, &vid, &pid, &override, &guid )) continue; + if (!hid_device_is_valid_joystick( tmp, &vid, &pid, &override )) continue; if (!(instance = malloc( sizeof(*instance) ))) { hr = E_OUTOFMEMORY; @@ -1918,7 +1942,6 @@ HRESULT hid_joystick_refresh_devices( void ) instance->vid = vid; instance->pid = pid; instance->override = override; - instance->guid = guid; wcscpy( instance->dev_path, tmp ); /* * SetupAPI returns paths in lowercase, cfgmgr32 does not. joy.cpl @@ -1956,7 +1979,6 @@ HRESULT hid_joystick_refresh_devices( void ) } id->assigned = TRUE; - if (IsEqualGUID( &id->guid, &GUID_NULL )) id->guid = cur->guid; cur->guid = id->guid; } diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 2c395cb4462..f2b9c490054 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6191,12 +6191,12 @@ static void test_joystick_instance_guid( DWORD version ) { winetest_push_context( "device %d", i ); - todo_wine ok( !IsEqualGUID( &expect_instances[i], &instances[i] ), + ok( !IsEqualGUID( &expect_instances[i], &instances[i] ), "Unexpected instance %s.\n", debugstr_guid( &instances[i] ) ); /* Old guidInstance no longer works. */ hr = dinput_create_device( &di, &expect_instances[i], &device ); - todo_wine ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); expect_instances[i] = instances[i]; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/joystick_hid.c | 2 +- dlls/dinput/tests/joystick8.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/dlls/dinput/joystick_hid.c b/dlls/dinput/joystick_hid.c index 5d21e993d10..7e49eefab37 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -1667,7 +1667,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 f2b9c490054..979e7585ead 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6267,7 +6267,7 @@ static void test_joystick_instance_guid( DWORD version ) IDirectInputDevice8_Release( device ); hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); hr = dinput_create_device( &di, &instances[1], &device ); @@ -6293,10 +6293,10 @@ static void test_joystick_instance_guid( DWORD version ) /* Stopped devices should return E_FAIL. */ hr = dinput_create_device( &di, &instances[0], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); hr = dinput_create_device( &di, &instances[2], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); /* After calling EnumDevices(), guidInstance values will be reassigned. */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
From: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/dinput.c | 9 +++++++- dlls/dinput/tests/joystick8.c | 40 ++++++++++++----------------------- 2 files changed, 22 insertions(+), 27 deletions(-) diff --git a/dlls/dinput/dinput.c b/dlls/dinput/dinput.c index 1695221d3a6..1670f0421f0 100644 --- a/dlls/dinput/dinput.c +++ b/dlls/dinput/dinput.c @@ -282,7 +282,14 @@ static HRESULT WINAPI dinput7_CreateDeviceEx( IDirectInput7W *iface, const GUID if (IsEqualGUID( &GUID_SysKeyboard, guid )) hr = keyboard_create_device( impl, guid, &device ); else if (IsEqualGUID( &GUID_SysMouse, guid )) hr = mouse_create_device( impl, guid, &device ); - else hr = hid_joystick_create_device( impl, guid, &device ); + else + { + if ((hr = hid_joystick_create_device( impl, guid, &device )) == DIERR_DEVICENOTREG) + { + hid_joystick_refresh_devices(); + hr = hid_joystick_create_device( impl, guid, &device ); + } + } if (FAILED(hr)) return hr; diff --git a/dlls/dinput/tests/joystick8.c b/dlls/dinput/tests/joystick8.c index 979e7585ead..8cfe90c9fce 100644 --- a/dlls/dinput/tests/joystick8.c +++ b/dlls/dinput/tests/joystick8.c @@ -6224,7 +6224,7 @@ static void test_joystick_instance_guid( DWORD version ) ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); instances_end = instances; @@ -6245,7 +6245,7 @@ static void test_joystick_instance_guid( DWORD version ) ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); + ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); instances_end = instances; @@ -6396,17 +6396,13 @@ static void test_joystick_instance_guid( DWORD version ) hid_device_start( &descs[i], 1 ); hr = dinput_create_device( &di, &expect_instances[i], &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); - if (SUCCEEDED(hr)) - { - check_device_hid_serial( device, descs[i].serial_str ); - IDirectInputDevice8_Release( device ); - } + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + check_device_hid_serial( device, descs[i].serial_str ); + IDirectInputDevice8_Release( device ); hid_device_stop( &descs[i], 1 ); hr = dinput_create_device( &di, &expect_instances[i], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); - if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); hid_device_start( &descs[i], 1 ); winetest_pop_context(); @@ -6433,17 +6429,13 @@ static void test_joystick_instance_guid( DWORD version ) hid_device_start( &descs[0], 1 ); hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); - if (SUCCEEDED(hr)) - { - check_device_hid_serial( device, descs[0].serial_str ); - IDirectInputDevice8_Release( device ); - } + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + check_device_hid_serial( device, descs[0].serial_str ); + IDirectInputDevice8_Release( device ); hid_device_stop( &descs[0], 1 ); hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); - if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); /* Start device 1. */ @@ -6454,8 +6446,7 @@ static void test_joystick_instance_guid( DWORD version ) * with device 0. */ hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); - if (SUCCEEDED(hr)) IDirectInputDevice8_Release( device ); + ok( hr == E_FAIL, "Unexpected hr %#lx.\n", hr ); /* * expect_instances[1] is not currently associated with a device, which @@ -6467,12 +6458,9 @@ static void test_joystick_instance_guid( DWORD version ) ok( hr == DIERR_DEVICENOTREG, "Unexpected hr %#lx.\n", hr ); hr = dinput_create_device( &di, &expect_instances[0], &device ); - todo_wine ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); - if (SUCCEEDED(hr)) - { - check_device_hid_serial( device, descs[1].serial_str ); - IDirectInputDevice8_Release( device ); - } + ok( hr == DI_OK, "Unexpected hr %#lx.\n", hr ); + check_device_hid_serial( device, descs[1].serial_str ); + IDirectInputDevice8_Release( device ); hid_device_stop( &descs[1], 1 ); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10364
On Mon Apr 20 14:08:01 2026 +0000, Connor McAdams wrote:
changed this line in [version 5 of the diff](/wine/wine/-/merge_requests/10364/diffs?diff_id=261726&start_sha=9cef9159d5dad342cb04cc69bcf554ae48fc1ea5#4ad1ac26b8a52443a124ce8c55def4aa2a4f5075_162_138) I've reworked things in the current revision, removing `DevicePath` from the registry completely because I think it was just causing more confusion.
The way it is done now is: - All existing registry entries are read into a list. - Enumerated devices check this list to find an existing VID/PID/index, if one exists, it is reused. - If no VID/PID/index pairing exists, a new guidInstance is created, and added to a separate list. - After all devices have been assigned a guidInstance, we check if there are any entries in the new guidInstance list, and write them to the registry. I think this method is clearer, and I've also tried splitting it a bit to make review easier. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10364#note_137098
On Mon Apr 20 14:08:01 2026 +0000, Connor McAdams wrote:
changed this line in [version 5 of the diff](/wine/wine/-/merge_requests/10364/diffs?diff_id=261726&start_sha=9cef9159d5dad342cb04cc69bcf554ae48fc1ea5#4ad1ac26b8a52443a124ce8c55def4aa2a4f5075_2027_1936) I've changed this now to be checked for disablement/override when enumerating over the currently connected HID devices, but deferring the handling of overridden devices until `EnumDevices()` or `CreateDevice()` is called.
AFAICT, an application will either get one device or the other (the IG device or the overridden one), so IMO it's fine for them to share the same `guidInstance` value, given they have the same VID/PID/index in both cases. I've also tested the override behavior in joy.cpl to confirm this is working properly. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10364#note_137099
participants (2)
-
Connor McAdams -
Connor McAdams (@cmcadams)