Adding support for trigger rumble on supported controllers and drivers.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/windows.gaming.input/gamepad.c | 30 +++- dlls/windows.gaming.input/provider.c | 189 +++++++++++++++++++++++++ dlls/windows.gaming.input/provider.idl | 11 ++ 3 files changed, 226 insertions(+), 4 deletions(-)
diff --git a/dlls/windows.gaming.input/gamepad.c b/dlls/windows.gaming.input/gamepad.c index 7e72609a277..0c38fb5cd1a 100644 --- a/dlls/windows.gaming.input/gamepad.c +++ b/dlls/windows.gaming.input/gamepad.c @@ -217,14 +217,36 @@ DEFINE_IINSPECTABLE_OUTER( gamepad, IGamepad, struct gamepad, IGameController_ou
static HRESULT WINAPI gamepad_get_Vibration( IGamepad *iface, struct GamepadVibration *value ) { - FIXME( "iface %p, value %p stub!\n", iface, value ); - return E_NOTIMPL; + struct gamepad *impl = impl_from_IGamepad( iface ); + struct WineGameControllerVibration vibration; + HRESULT hr; + + TRACE( "iface %p, value %p.\n", iface, value ); + + if (FAILED(hr = IWineGameControllerProvider_get_Vibration( impl->wine_provider, &vibration ))) return hr; + + value->LeftMotor = vibration.rumble / 65535.; + value->RightMotor = vibration.buzz / 65535.; + value->LeftTrigger = vibration.left / 65535.; + value->RightTrigger = vibration.right / 65535.; + + return S_OK; }
static HRESULT WINAPI gamepad_put_Vibration( IGamepad *iface, struct GamepadVibration value ) { - FIXME( "iface %p, value %p stub!\n", iface, &value ); - return E_NOTIMPL; + struct gamepad *impl = impl_from_IGamepad( iface ); + struct WineGameControllerVibration vibration = + { + .rumble = value.LeftMotor * 65535., + .buzz = value.RightMotor * 65535., + .left = value.LeftTrigger * 65535., + .right = value.RightTrigger * 65535., + }; + + TRACE( "iface %p, value %p.\n", iface, &value ); + + return IWineGameControllerProvider_put_Vibration( impl->wine_provider, vibration ); }
static HRESULT WINAPI gamepad_GetCurrentReading( IGamepad *iface, struct GamepadReading *value ) diff --git a/dlls/windows.gaming.input/provider.c b/dlls/windows.gaming.input/provider.c index 0877938f774..35da3486592 100644 --- a/dlls/windows.gaming.input/provider.c +++ b/dlls/windows.gaming.input/provider.c @@ -20,8 +20,10 @@ #include "private.h"
#include "initguid.h" +#include "ddk/hidsdi.h" #include "dinput.h" #include "provider.h" +#include "hidusage.h"
#include "wine/debug.h"
@@ -49,6 +51,18 @@ struct provider IDirectInputDevice8W *dinput_device; WCHAR device_path[MAX_PATH]; struct list entry; + + struct WineGameControllerVibration vibration; + + char *report_buf; + PHIDP_PREPARSED_DATA preparsed; + HIDP_VALUE_CAPS haptics_rumble_caps; + HIDP_VALUE_CAPS haptics_buzz_caps; + HIDP_VALUE_CAPS haptics_left_caps; + HIDP_VALUE_CAPS haptics_right_caps; + BYTE haptics_report; + HIDP_CAPS caps; + HANDLE device; };
static inline struct provider *impl_from_IWineGameControllerProvider( IWineGameControllerProvider *iface ) @@ -100,6 +114,9 @@ static ULONG WINAPI wine_provider_Release( IWineGameControllerProvider *iface ) if (!ref) { IDirectInputDevice8_Release( impl->dinput_device ); + HidD_FreePreparsedData( impl->preparsed ); + CloseHandle( impl->device ); + free( impl->report_buf ); free( impl ); }
@@ -245,6 +262,58 @@ static HRESULT WINAPI wine_provider_get_State( IWineGameControllerProvider *ifac return S_OK; }
+static HRESULT WINAPI wine_provider_get_Vibration( IWineGameControllerProvider *iface, struct WineGameControllerVibration *out ) +{ + struct provider *impl = impl_from_IWineGameControllerProvider( iface ); + TRACE( "iface %p, out %p.\n", iface, out ); + *out = impl->vibration; + return S_OK; +} + +static HRESULT WINAPI wine_provider_put_Vibration( IWineGameControllerProvider *iface, struct WineGameControllerVibration value ) +{ + struct provider *impl = impl_from_IWineGameControllerProvider( iface ); + ULONG report_len = impl->caps.OutputReportByteLength; + PHIDP_PREPARSED_DATA preparsed = impl->preparsed; + char *report_buf = impl->report_buf; + USHORT collection; + NTSTATUS status; + BOOL ret; + + TRACE( "iface %p, value %p.\n", iface, &value ); + + if (!memcmp( &impl->vibration, &value, sizeof(value) )) return S_OK; + impl->vibration = value; + + status = HidP_InitializeReportForID( HidP_Output, impl->haptics_report, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_InitializeReportForID returned %#lx\n", status ); + + collection = impl->haptics_rumble_caps.LinkCollection; + status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY, + impl->vibration.rumble, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status ); + + collection = impl->haptics_buzz_caps.LinkCollection; + status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY, + impl->vibration.buzz, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status ); + + collection = impl->haptics_left_caps.LinkCollection; + status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY, + impl->vibration.left, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status ); + + collection = impl->haptics_right_caps.LinkCollection; + status = HidP_SetUsageValue( HidP_Output, HID_USAGE_PAGE_HAPTICS, collection, HID_USAGE_HAPTICS_INTENSITY, + impl->vibration.right, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_SetUsageValue INTENSITY returned %#lx\n", status ); + + ret = HidD_SetOutputReport( impl->device, report_buf, report_len ); + if (!ret) WARN( "HidD_SetOutputReport failed with error %lu\n", GetLastError() ); + + return S_OK; +} + static const struct IWineGameControllerProviderVtbl wine_provider_vtbl = { wine_provider_QueryInterface, @@ -260,6 +329,8 @@ static const struct IWineGameControllerProviderVtbl wine_provider_vtbl = wine_provider_get_ButtonCount, wine_provider_get_SwitchCount, wine_provider_get_State, + wine_provider_get_Vibration, + wine_provider_put_Vibration, };
DEFINE_IINSPECTABLE( game_provider, IGameControllerProvider, struct provider, IWineGameControllerProvider_iface ) @@ -325,6 +396,122 @@ static const struct IGameControllerProviderVtbl game_provider_vtbl = game_provider_get_IsConnected, };
+static void check_haptics_caps( struct provider *provider, HANDLE device, PHIDP_PREPARSED_DATA preparsed, + HIDP_LINK_COLLECTION_NODE *collections, HIDP_VALUE_CAPS *caps ) +{ + USHORT count, report_len = provider->caps.FeatureReportByteLength; + ULONG parent = caps->LinkCollection, waveform = 0; + char *report_buf = provider->report_buf; + HIDP_VALUE_CAPS value_caps; + USAGE_AND_PAGE phy_usages; + NTSTATUS status; + + while (collections[parent].LinkUsagePage != HID_USAGE_PAGE_HAPTICS || + collections[parent].LinkUsage != HID_USAGE_HAPTICS_SIMPLE_CONTROLLER) + if (!(parent = collections[parent].Parent)) break; + + if (collections[parent].LinkUsagePage != HID_USAGE_PAGE_HAPTICS || + collections[parent].LinkUsage != HID_USAGE_HAPTICS_SIMPLE_CONTROLLER) + { + WARN( "Failed to find haptics simple controller collection\n" ); + return; + } + phy_usages.UsagePage = collections[collections[parent].Parent].LinkUsagePage; + phy_usages.Usage = collections[collections[parent].Parent].LinkUsage; + + status = HidP_InitializeReportForID( HidP_Feature, caps->ReportID, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_InitializeReportForID returned %#lx\n", status ); + if (!HidD_GetFeature( device, report_buf, report_len )) + { + WARN( "Failed to get waveform list report, error %lu\n", GetLastError() ); + return; + } + + status = HidP_GetUsageValue( HidP_Feature, caps->UsagePage, caps->LinkCollection, + caps->NotRange.Usage, &waveform, preparsed, report_buf, report_len ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetUsageValue returned %#lx\n", status ); + + count = 1; + status = HidP_GetSpecificValueCaps( HidP_Output, HID_USAGE_PAGE_HAPTICS, parent, + HID_USAGE_HAPTICS_INTENSITY, &value_caps, &count, preparsed ); + if (status != HIDP_STATUS_SUCCESS || !count) WARN( "Failed to get waveform intensity caps, status %#lx\n", status ); + else if (phy_usages.UsagePage == HID_USAGE_PAGE_GENERIC && phy_usages.Usage == HID_USAGE_GENERIC_Z) + { + TRACE( "Found left rumble caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection ); + provider->haptics_report = value_caps.ReportID; + provider->haptics_left_caps = value_caps; + } + else if (phy_usages.UsagePage == HID_USAGE_PAGE_GENERIC && phy_usages.Usage == HID_USAGE_GENERIC_RZ) + { + TRACE( "Found right rumble caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection ); + provider->haptics_report = value_caps.ReportID; + provider->haptics_right_caps = value_caps; + } + else if (waveform == HID_USAGE_HAPTICS_WAVEFORM_RUMBLE) + { + TRACE( "Found rumble caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection ); + provider->haptics_report = value_caps.ReportID; + provider->haptics_rumble_caps = value_caps; + } + else if (waveform == HID_USAGE_HAPTICS_WAVEFORM_BUZZ) + { + TRACE( "Found buzz caps, report %u collection %u\n", value_caps.ReportID, value_caps.LinkCollection ); + provider->haptics_report = value_caps.ReportID; + provider->haptics_buzz_caps = value_caps; + } + else FIXME( "Unsupported waveform type %#lx\n", waveform ); +} + +static void open_haptics_device( struct provider *provider ) +{ + HIDP_LINK_COLLECTION_NODE *collections; + PHIDP_PREPARSED_DATA preparsed = NULL; + ULONG i, size, coll_count = 0; + USHORT count, caps_count = 0; + HIDP_VALUE_CAPS caps[8]; + NTSTATUS status; + HANDLE device; + + device = CreateFileW( provider->device_path, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, + NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, 0 ); + if (device == INVALID_HANDLE_VALUE) return; + + if (!HidD_GetPreparsedData( device, &preparsed )) goto failed; + if (HidP_GetCaps( preparsed, &provider->caps ) != HIDP_STATUS_SUCCESS) goto failed; + + size = max( provider->caps.OutputReportByteLength, provider->caps.FeatureReportByteLength ); + if (!(provider->report_buf = malloc( size ))) goto failed; + + coll_count = provider->caps.NumberLinkCollectionNodes; + if (!(collections = malloc( sizeof(*collections) * coll_count ))) goto failed; + + status = HidP_GetLinkCollectionNodes( collections, &coll_count, preparsed ); + if (status != HIDP_STATUS_SUCCESS) WARN( "HidP_GetLinkCollectionNodes returned %#lx\n", status ); + else for (i = 0; i < coll_count; ++i) + { + if (collections[i].LinkUsagePage != HID_USAGE_PAGE_HAPTICS) continue; + if (collections[i].LinkUsage == HID_USAGE_HAPTICS_WAVEFORM_LIST) + { + count = ARRAY_SIZE(caps) - caps_count; + status = HidP_GetSpecificValueCaps( HidP_Feature, HID_USAGE_PAGE_ORDINAL, i, 0, + caps + caps_count, &count, preparsed ); + if (status == HIDP_STATUS_SUCCESS) caps_count += count; + } + } + for (i = 0; i < caps_count; ++i) check_haptics_caps( provider, device, preparsed, collections, caps + i ); + free( collections ); + + provider->preparsed = preparsed; + provider->device = device; + return; + +failed: + free( provider->report_buf ); + provider->report_buf = NULL; + HidD_FreePreparsedData( preparsed ); + CloseHandle( device ); +} + void provider_create( const WCHAR *device_path ) { IDirectInputDevice8W *dinput_device; @@ -361,6 +548,8 @@ void provider_create( const WCHAR *device_path )
wcscpy( impl->device_path, device_path ); list_init( &impl->entry ); + open_haptics_device( impl ); + provider = &impl->IGameControllerProvider_iface; TRACE( "created WineGameControllerProvider %p\n", provider );
diff --git a/dlls/windows.gaming.input/provider.idl b/dlls/windows.gaming.input/provider.idl index 25191078721..f2ff4da4ebd 100644 --- a/dlls/windows.gaming.input/provider.idl +++ b/dlls/windows.gaming.input/provider.idl @@ -33,6 +33,7 @@ import "windows.gaming.input.custom.idl"; namespace Windows.Gaming.Input.Custom { typedef enum WineGameControllerType WineGameControllerType; typedef struct WineGameControllerState WineGameControllerState; + typedef struct WineGameControllerVibration WineGameControllerVibration; interface IWineGameControllerProvider; runtimeclass WineGameControllerProvider;
@@ -50,6 +51,14 @@ namespace Windows.Gaming.Input.Custom { Windows.Gaming.Input.GameControllerSwitchPosition switches[4]; };
+ struct WineGameControllerVibration + { + UINT16 rumble; + UINT16 buzz; + UINT16 left; + UINT16 right; + }; + [ uuid(06e58977-7684-4dc5-bad1-cda52a4aa06d) ] @@ -73,6 +82,8 @@ namespace Windows.Gaming.Input.Custom { [propget] HRESULT SwitchCount([out, retval] INT32 *value);
[propget] HRESULT State([out, retval] WineGameControllerState *state); + [propget] HRESULT Vibration([out, retval] WineGameControllerVibration *vibration); + [propput] HRESULT Vibration([in] WineGameControllerVibration vibration); }
[