windows.gaming.input: Implement HID simple haptics controllers support.

Adding support for trigger rumble on supported controllers and drivers.

Signed-off-by: Rémi Bernon <rbernon@codeweavers.com>
Signed-off-by: Alexandre Julliard <julliard@winehq.org>
This commit is contained in:
Rémi Bernon 2022-03-15 10:08:28 +01:00 committed by Alexandre Julliard
parent 634d380e4c
commit befeea424e
3 changed files with 226 additions and 4 deletions

View File

@ -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 )

View File

@ -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 );

View File

@ -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);
}
[