From: Connor McAdams <cmcadams@codeweavers.com> A local cache of joystick instances is also maintained. Signed-off-by: Connor McAdams <cmcadams@codeweavers.com> --- dlls/dinput/Makefile.in | 2 +- dlls/dinput/dinput.c | 1 + dlls/dinput/dinput_main.c | 1 + dlls/dinput/dinput_private.h | 2 + dlls/dinput/joystick_hid.c | 468 ++++++++++++++++++++++++++++++++++- dlls/dinput8/Makefile.in | 2 +- 6 files changed, 473 insertions(+), 3 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/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..aabad4ff97a 100644 --- a/dlls/dinput/dinput_main.c +++ b/dlls/dinput/dinput_main.c @@ -522,6 +522,7 @@ BOOL WINAPI DllMain( HINSTANCE inst, DWORD reason, void *reserved ) break; case DLL_PROCESS_DETACH: if (reserved) break; + hid_joystick_cleanup(); unregister_di_em_win_class(); break; } diff --git a/dlls/dinput/dinput_private.h b/dlls/dinput/dinput_private.h index 0609b816b3b..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..e297f393a60 100644 --- a/dlls/dinput/joystick_hid.c +++ b/dlls/dinput/joystick_hid.c @@ -36,12 +36,14 @@ #include "setupapi.h" #include "devguid.h" #include "dinput.h" -#include "setupapi.h" +#include "cfgmgr32.h" #include "dinput_private.h" #include "device_private.h" #include "initguid.h" +#include "devpkey.h" +#include "propkey.h" #include "wine/debug.h" #include "wine/hid.h" @@ -52,6 +54,294 @@ 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 ); +/* + * Version 1 UUID timestamps are a count of the number of 100 nanosecond + * intervals since 00:00:00.00, 15 October 1582 (the date of Gregorian + * reform to the Christian calendar). FILETIME is a count of the number of 100 + * nanosecond intervals since 00:00:00.00, 1 January 1601. In order to convert + * a FILETIME value to a UUID timestamp, we need to add: + * - 17 days in October 1582. + * - 30 days in November 1582. + * - 31 days in December 1582. + * - 18 years between January 1583 and January 1601. + * - 5 leap days in those 18 years. + */ +#define UUID_TIME_TO_SYSTEM_TIME_DAYS ((ULONG64)((365 * 18) + 5 + 17 + 30 + 31)) +#define UUID_TIME_TO_SYSTEM_TIME_NS_DIFFERENCE ((UUID_TIME_TO_SYSTEM_TIME_DAYS) * 24 * 60 * 60 * 10000000) +DEFINE_GUID( dinput_joystick_uuid_init, 0x00000000, 0x0000, 0x1000, 0x80, 0x00, 0x00, 0x00, 'D', 'E', 'S', 'T' ); +static void create_v1_uuid(GUID *guid) +{ + GUID tmp = dinput_joystick_uuid_init; + static LONG clock_seq; + ULARGE_INTEGER time; + ULONG cur_seq; + + GetSystemTimeAsFileTime( (FILETIME *)&time ); + time.QuadPart += UUID_TIME_TO_SYSTEM_TIME_NS_DIFFERENCE; + tmp.Data1 = (time.QuadPart & 0xffffffff); + tmp.Data2 = ((time.QuadPart >> 32) & 0xffff); + tmp.Data3 |= ((time.QuadPart >> 48) & 0x0fff); + cur_seq = InterlockedIncrement( &clock_seq ); + tmp.Data4[1] |= (cur_seq & 0xff); + tmp.Data4[0] |= ((cur_seq & 0x3f00) >> 8); + *guid = tmp; +} + +static HANDLE dinput_reg_mutex; +static BOOL WINAPI dinput_init_reg_mutex(INIT_ONCE *once, void *param, void **ctx) +{ + HANDLE mutex = CreateMutexW( NULL, FALSE, L"__wine_dinput_reg_mutex" ); + + if (mutex) dinput_reg_mutex = mutex; + + return !!mutex; +} + +static void get_dinput_reg_mutex( void ) +{ + static INIT_ONCE once = INIT_ONCE_STATIC_INIT; + + if (!dinput_reg_mutex) InitOnceExecuteOnce( &once, dinput_init_reg_mutex, NULL, NULL ); + WaitForSingleObject( dinput_reg_mutex, INFINITE ); +} + +static void release_dinput_reg_mutex( void ) +{ + assert( dinput_reg_mutex ); + ReleaseMutex( dinput_reg_mutex ); +} + +struct joystick_instance +{ + struct list entry; + WORD vid, pid; + GUID guid; + + WCHAR dev_path[MAX_PATH]; +}; + +static struct joystick_instance *joystick_instance_create( WORD vid, WORD pid, const GUID *guid, const WCHAR *path ) +{ + struct joystick_instance *inst = calloc( 1, sizeof(*inst) ); + + if (inst) + { + inst->vid = vid; + inst->pid = pid; + if (guid) inst->guid = *guid; + wcscpy( inst->dev_path, path ); + } + + return inst; +} + +/* Need to enter the mutex prior to accessing these registry entries. */ +static const WCHAR *dinput_path = L"System\\CurrentControlSet\\Control\\MediaProperties\\" + "PrivateProperties\\DirectInput"; +static void reset_joystick_instances_in_registry(void) +{ + WCHAR buf[MAX_PATH]; + HKEY root_key; + + if (RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) return; + + for (UINT i = 0; !RegEnumKeyW( root_key, i, buf, ARRAY_SIZE(buf) ); i++) + { + HKEY dev_key; + + 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++) + RegDeleteKeyValueW( dev_key, buf, L"DevicePath" ); + + RegCloseKey( dev_key ); + } + RegCloseKey( root_key ); +} + +static HKEY get_empty_joystick_instance_in_registry(WORD vid, WORD pid) +{ + HKEY root_key, dev_key = NULL; + WCHAR buf[MAX_PATH]; + LSTATUS ret; + DWORD len; + UINT i; + + swprintf( buf, ARRAY_SIZE(buf), L"%s\\VID_%04X&PID_%04X\\Calibration", dinput_path, vid, pid ); + if (RegCreateKeyExW( HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) + { + ERR( "Failed to create key.\n" ); + return NULL; + } + + for (i = 0; !RegEnumKeyW( root_key, i, buf, ARRAY_SIZE(buf) ); i++) + { + if ((ret = RegGetValueW( root_key, buf, L"DevicePath", RRF_RT_REG_SZ, NULL, NULL, &len ))) + { + if (ret != ERROR_FILE_NOT_FOUND) WARN( "Got unexpected error %#lx.\n", ret ); + if (!RegOpenKeyExW( root_key, buf, 0, KEY_ALL_ACCESS, &dev_key )) break; + } + } + + if (!dev_key) + { + swprintf( buf, ARRAY_SIZE(buf), L"%s\\VID_%04X&PID_%04X\\Calibration\\%d", dinput_path, vid, pid, i ); + if ((ret = RegCreateKeyExW( HKEY_CURRENT_USER, buf, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &dev_key, NULL ))) + WARN( "Got unexpected error %#lx when creating key.\n", ret ); + } + RegCloseKey( root_key ); + return dev_key; +} + +static void set_joystick_instance_in_registry( struct joystick_instance *instance ) +{ + HKEY dev_key = get_empty_joystick_instance_in_registry( instance->vid, instance->pid ); + DWORD len = sizeof(instance->guid); + LSTATUS ret; + + if (!dev_key) + { + ERR( "Failed to get device instance in registry.\n" ); + return; + } + + /* If the instance GUID doesn't exist already, create it. */ + if ((ret = RegGetValueW( dev_key, NULL, L"GUID", RRF_RT_REG_BINARY, NULL, (BYTE *)&instance->guid, &len )) + || (len != sizeof(instance->guid))) + { + if (ret && ret != ERROR_FILE_NOT_FOUND) + WARN( "Got unexpected error %#lx.\n", ret ); + if (!ret && (len != sizeof(instance->guid))) + WARN( "GUID key value was invalid size %#lx, recreating value.\n", len ); + + create_v1_uuid( &instance->guid ); + if ((ret = RegSetValueExW( dev_key, L"GUID", 0, REG_BINARY, (const BYTE *)&instance->guid, + sizeof(instance->guid) ))) + WARN( "Failed to set instance GUID value in registry with error %#lx.\n", ret ); + } + + if ((ret = RegSetValueExW( dev_key, L"DevicePath", 0, REG_SZ, (const BYTE *)instance->dev_path, + (wcslen(instance->dev_path) + 1) * sizeof(WCHAR) ))) + WARN( "Failed to set device path in registry with error %#lx.\n", ret ); + + RegCloseKey( dev_key ); +} + +/* Need to enter the mutex to access this list. */ +static struct list joystick_cache = LIST_INIT(joystick_cache); + +/* + * Read current joystick instances in the registry, update any instances + * already in the local cache, and remove any instances in the local cache + * that no longer exist in the registry. + */ +static void hid_joystick_cache_update_from_registry( void ) +{ + struct joystick_instance *cur, *cur2; + struct list cur_joysticks; + WCHAR buf[MAX_PATH]; + HKEY root_key; + + if (RegCreateKeyExW( HKEY_CURRENT_USER, dinput_path, 0, NULL, 0, KEY_ALL_ACCESS, NULL, &root_key, NULL )) return; + + /* + * Store the current cache entries in a new list and remove them as we + * find them in the registry. + */ + list_init( &cur_joysticks ); + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &joystick_cache, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + list_add_tail( &cur_joysticks, &cur->entry ); + } + + 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++) + { + struct joystick_instance *instance = NULL; + WCHAR dev_path[MAX_PATH] = { 0 }; + DWORD len; + GUID guid; + + 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; + } + + len = sizeof(dev_path); + RegGetValueW( dev_key, buf, L"DevicePath", RRF_RT_REG_SZ, NULL, dev_path, &len ); + /* + * No device path value in registry, should be removed from local + * cache. + */ + if (!wcslen( dev_path )) continue; + + /* + * Attempt to find an entry from the local cache that matches this + * registry entry. + */ + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &cur_joysticks, struct joystick_instance, entry ) + { + if (IsEqualGUID( &cur->guid, &guid )) + { + wcscpy( cur->dev_path, dev_path ); + list_remove( &cur->entry ); + instance = cur; + break; + } + } + + /* No preexisting instance, create one. */ + if (!instance) + { + if (!(instance = joystick_instance_create( vid, pid, &guid, dev_path ))) + { + ERR( "Failed to allocate memory for device instance, expect problems.\n" ); + continue; + } + } + + list_add_tail( &joystick_cache, &instance->entry ); + } + RegCloseKey( dev_key ); + } + + /* + * Anything left in this list was in the local cache but not the registry, + * which means we should remove it. + */ + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &cur_joysticks, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + free( cur ); + } + RegCloseKey( root_key ); +} + +void hid_joystick_cleanup( void ) +{ + struct joystick_instance *cur, *cur2; + + if (dinput_reg_mutex) CloseHandle( dinput_reg_mutex ); + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &joystick_cache, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + free( cur ); + } +} + struct pid_control_report { BYTE id; @@ -1572,6 +1862,182 @@ failed: return DIERR_DEVICENOTREG; } +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; + ULONG size = 0; + CONFIGRET ret; + + *list = NULL; + while (1) + { + 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, instance_id, 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_joystick_validate( const WCHAR *path, WORD *vid, WORD *pid, WCHAR *instance_name ) +{ + 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 (vid) *vid = attrs.VendorID; + if (pid) *pid = attrs.ProductID; + if (instance_name) wcscpy( instance_name, product ); + ret_val = TRUE; + +exit: + HidD_FreePreparsedData( preparsed ); + CloseHandle( device_file ); + return ret_val; +} + +HRESULT hid_joystick_refresh_devices( void ) +{ + struct joystick_instance *cur, *cur2; + BOOL cache_changed = FALSE; + struct list cur_joysticks; + 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, NULL, &hid_list ); + if (FAILED(hr)) return hr; + + get_dinput_reg_mutex(); + hid_joystick_cache_update_from_registry(); + + list_init( &cur_joysticks ); + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &joystick_cache, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + list_add_tail( &cur_joysticks, &cur->entry ); + } + + /* + * Iterate over connected HID devices and check them against the joystick + * cache list. + * If the local cache matches the currently present HID devices, nothing + * is changed. + * If the local cache does not match, missing devices are removed, any new + * devices are added, and the registry will be updated. + */ + for (tmp = hid_list; *tmp; tmp = tmp + wcslen( tmp ) + 1) + { + WCHAR instance_id[MAX_DEVICE_ID_LEN], product[MAX_PATH], path[MAX_PATH]; + struct joystick_instance *instance = NULL; + BOOL override = FALSE; + DEVPROPTYPE type; + CONFIGRET ret; + WORD vid, pid; + WCHAR *tmp2; + ULONG size; + + wcscpy( path, tmp ); + memset( product, 0, sizeof(product) ); + if (!hid_joystick_validate( path, &vid, &pid, product )) continue; + if (device_instance_is_disabled( product, &override )) continue; + + ret = CM_Get_Device_Interface_PropertyW( path, &DEVPKEY_Device_InstanceId, &type, (BYTE *)instance_id, &size, 0 ); + if (!ret && override && (tmp2 = wcsstr( instance_id, L"&IG_" ))) + { + WCHAR *xi_iface; + + memcpy( tmp2, L"&XI_", sizeof(L"&XI_") - sizeof(WCHAR) ); + if (SUCCEEDED(hid_joystick_get_class_dev_iface_list( &GUID_DEVINTERFACE_WINEXINPUT, instance_id, &xi_iface ))) + { + wcscpy( path, xi_iface ); + free( xi_iface ); + if (!hid_joystick_validate( path, NULL, NULL, NULL )) continue; + } + } + + /* Try to find a preexisting joystick instance for this device. */ + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &cur_joysticks, struct joystick_instance, entry ) + { + if ((cur->vid == vid) && (cur->pid == pid)) + { + list_remove( &cur->entry ); + instance = cur; + break; + } + } + + /* No preexisting instance for this device, create one. */ + if (!instance) + { + if (!(instance = joystick_instance_create( vid, pid, NULL, path ))) + { + hr = E_OUTOFMEMORY; + goto exit; + } + cache_changed = TRUE; + } + else if (wcscmp(instance->dev_path, path)) + { + cache_changed = TRUE; + wcscpy(instance->dev_path, path); + } + + list_add_tail( &joystick_cache, &instance->entry ); + } + + /* Something changed, update the registry values. */ + if (cache_changed || !list_empty(&cur_joysticks)) + { + reset_joystick_instances_in_registry(); + LIST_FOR_EACH_ENTRY( cur, &joystick_cache, struct joystick_instance, entry ) + set_joystick_instance_in_registry( cur ); + } + +exit: + release_dinput_reg_mutex(); + LIST_FOR_EACH_ENTRY_SAFE( cur, cur2, &cur_joysticks, struct joystick_instance, entry ) + { + list_remove( &cur->entry ); + free(cur); + } + free( hid_list ); + 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 ) 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