[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