[PATCH 4/4] windows.gaming.input: Implement HID simple haptics controllers support.
Rémi Bernon
rbernon at codeweavers.com
Tue Mar 15 04:08:28 CDT 2022
Adding support for trigger rumble on supported controllers and drivers.
Signed-off-by: Rémi Bernon <rbernon at 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);
}
[
--
2.35.1
More information about the wine-devel
mailing list