Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winebus.sys/bus_sdl.c | 7 +++--- dlls/winebus.sys/bus_udev.c | 7 +++--- dlls/winebus.sys/hid.c | 44 +++++++++++++++++++++++++++++++-- dlls/winebus.sys/unix_private.h | 5 +++- dlls/winebus.sys/unixlib.c | 6 +++-- 5 files changed, 58 insertions(+), 11 deletions(-)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index aedef50d3f7..aab629de5da 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -432,13 +432,14 @@ static void sdl_device_stop(struct unix_device *iface) }
static NTSTATUS sdl_device_haptics_start(struct unix_device *iface, UINT duration_ms, - USHORT rumble_intensity, USHORT buzz_intensity) + USHORT rumble_intensity, USHORT buzz_intensity, + USHORT left_intensity, USHORT right_intensity) { struct sdl_device *impl = impl_from_unix_device(iface); SDL_HapticEffect effect;
- TRACE("iface %p, duration_ms %u, rumble_intensity %u, buzz_intensity %u.\n", iface, duration_ms, - rumble_intensity, buzz_intensity); + TRACE("iface %p, duration_ms %u, rumble_intensity %u, buzz_intensity %u, left_intensity %u, right_intensity %u.\n", + iface, duration_ms, rumble_intensity, buzz_intensity, left_intensity, right_intensity);
if (!(impl->effect_support & EFFECT_SUPPORT_HAPTICS)) return STATUS_NOT_SUPPORTED;
diff --git a/dlls/winebus.sys/bus_udev.c b/dlls/winebus.sys/bus_udev.c index db173b63fb5..6b4bf09a773 100644 --- a/dlls/winebus.sys/bus_udev.c +++ b/dlls/winebus.sys/bus_udev.c @@ -867,7 +867,8 @@ static void lnxev_device_read_report(struct unix_device *iface) }
static NTSTATUS lnxev_device_haptics_start(struct unix_device *iface, UINT duration_ms, - USHORT rumble_intensity, USHORT buzz_intensity) + USHORT rumble_intensity, USHORT buzz_intensity, + USHORT left_intensity, USHORT right_intensity) { struct lnxev_device *impl = lnxev_impl_from_unix_device(iface); struct ff_effect effect = @@ -877,8 +878,8 @@ static NTSTATUS lnxev_device_haptics_start(struct unix_device *iface, UINT durat }; struct input_event event;
- TRACE("iface %p, duration_ms %u, rumble_intensity %u, buzz_intensity %u.\n", iface, - duration_ms, rumble_intensity, buzz_intensity); + TRACE("iface %p, duration_ms %u, rumble_intensity %u, buzz_intensity %u, left_intensity %u, right_intensity %u.\n", + iface, duration_ms, rumble_intensity, buzz_intensity, left_intensity, right_intensity);
effect.replay.length = duration_ms; effect.u.rumble.strong_magnitude = rumble_intensity; diff --git a/dlls/winebus.sys/hid.c b/dlls/winebus.sys/hid.c index 5b18da5d8ed..de0c3bb0848 100644 --- a/dlls/winebus.sys/hid.c +++ b/dlls/winebus.sys/hid.c @@ -335,6 +335,8 @@ struct hid_haptics_intensity { UINT16 rumble_intensity; UINT16 buzz_intensity; + UINT16 left_intensity; + UINT16 right_intensity; }; #include "poppack.h"
@@ -389,6 +391,15 @@ BOOL hid_device_add_haptics(struct unix_device *iface) OUTPUT(1, Data|Var|Abs), END_COLLECTION, }; + const BYTE trigger_template_begin[] = + { + USAGE_PAGE(1, HID_USAGE_PAGE_GENERIC), + COLLECTION(1, Physical), + }; + const BYTE trigger_template_end[] = + { + END_COLLECTION, + };
iface->hid_haptics.features_report = haptics_features_report; iface->hid_haptics.intensity_report = haptics_intensity_report; @@ -398,12 +409,36 @@ BOOL hid_device_add_haptics(struct unix_device *iface) iface->hid_haptics.features.buzz.waveform = HID_USAGE_HAPTICS_WAVEFORM_BUZZ; iface->hid_haptics.features.buzz.duration = 0; iface->hid_haptics.features.buzz.cutoff_time_ms = 1000; + iface->hid_haptics.features.left.waveform = HID_USAGE_HAPTICS_WAVEFORM_RUMBLE; + iface->hid_haptics.features.left.duration = 0; + iface->hid_haptics.features.left.cutoff_time_ms = 1000; + iface->hid_haptics.features.right.waveform = HID_USAGE_HAPTICS_WAVEFORM_RUMBLE; + iface->hid_haptics.features.right.duration = 0; + iface->hid_haptics.features.right.cutoff_time_ms = 1000;
if (!hid_report_descriptor_append(desc, haptics_template, sizeof(haptics_template))) return FALSE; if (!hid_report_descriptor_append(desc, haptics_template, sizeof(haptics_template))) return FALSE;
+ if (!hid_report_descriptor_append_usage(desc, HID_USAGE_GENERIC_Z)) + return FALSE; + if (!hid_report_descriptor_append(desc, trigger_template_begin, sizeof(trigger_template_begin))) + return FALSE; + if (!hid_report_descriptor_append(desc, haptics_template, sizeof(haptics_template))) + return FALSE; + if (!hid_report_descriptor_append(desc, trigger_template_end, sizeof(trigger_template_end))) + return FALSE; + + if (!hid_report_descriptor_append_usage(desc, HID_USAGE_GENERIC_RZ)) + return FALSE; + if (!hid_report_descriptor_append(desc, trigger_template_begin, sizeof(trigger_template_begin))) + return FALSE; + if (!hid_report_descriptor_append(desc, haptics_template, sizeof(haptics_template))) + return FALSE; + if (!hid_report_descriptor_append(desc, trigger_template_end, sizeof(trigger_template_end))) + return FALSE; + return TRUE; }
@@ -1071,12 +1106,15 @@ static void hid_device_set_output_report(struct unix_device *iface, HID_XFER_PAC io->Information = sizeof(*report) + 1; assert(packet->reportBufferLen == io->Information);
- if (!report->rumble_intensity && !report->buzz_intensity) + if (!report->rumble_intensity && !report->buzz_intensity && !report->left_intensity && !report->right_intensity) io->Status = iface->hid_vtbl->haptics_stop(iface); else { duration_ms = min(haptics->features.rumble.cutoff_time_ms, haptics->features.buzz.cutoff_time_ms); - io->Status = iface->hid_vtbl->haptics_start(iface, duration_ms, report->rumble_intensity, report->buzz_intensity); + duration_ms = min(duration_ms, haptics->features.left.cutoff_time_ms); + duration_ms = min(duration_ms, haptics->features.right.cutoff_time_ms); + io->Status = iface->hid_vtbl->haptics_start(iface, duration_ms, report->rumble_intensity, report->buzz_intensity, + report->left_intensity, report->right_intensity); } } else if (packet->reportId == physical->device_control_report) @@ -1287,6 +1325,8 @@ static void hid_device_set_feature_report(struct unix_device *iface, HID_XFER_PA
haptics->features.rumble.cutoff_time_ms = features->rumble.cutoff_time_ms; haptics->features.buzz.cutoff_time_ms = features->buzz.cutoff_time_ms; + haptics->features.left.cutoff_time_ms = features->left.cutoff_time_ms; + haptics->features.right.cutoff_time_ms = features->right.cutoff_time_ms; io->Status = STATUS_SUCCESS; } else diff --git a/dlls/winebus.sys/unix_private.h b/dlls/winebus.sys/unix_private.h index 0763d084823..ab8060611a4 100644 --- a/dlls/winebus.sys/unix_private.h +++ b/dlls/winebus.sys/unix_private.h @@ -107,7 +107,8 @@ struct hid_device_vtbl NTSTATUS (*start)(struct unix_device *iface); void (*stop)(struct unix_device *iface); NTSTATUS (*haptics_start)(struct unix_device *iface, UINT duration_ms, - USHORT rumble_intensity, USHORT buzz_intensity); + USHORT rumble_intensity, USHORT buzz_intensity, + USHORT left_intensity, USHORT right_intensity); NTSTATUS (*haptics_stop)(struct unix_device *iface); NTSTATUS (*physical_device_control)(struct unix_device *iface, USAGE control); NTSTATUS (*physical_device_set_gain)(struct unix_device *iface, BYTE percent); @@ -135,6 +136,8 @@ struct hid_haptics_features { struct hid_haptics_feature rumble; struct hid_haptics_feature buzz; + struct hid_haptics_feature left; + struct hid_haptics_feature right; }; #include "poppack.h"
diff --git a/dlls/winebus.sys/unixlib.c b/dlls/winebus.sys/unixlib.c index 88cc513125a..1edd90627e5 100644 --- a/dlls/winebus.sys/unixlib.c +++ b/dlls/winebus.sys/unixlib.c @@ -103,7 +103,8 @@ static void mouse_stop(struct unix_device *iface) }
static NTSTATUS mouse_haptics_start(struct unix_device *iface, UINT duration, - USHORT rumble_intensity, USHORT buzz_intensity) + USHORT rumble_intensity, USHORT buzz_intensity, + USHORT left_intensity, USHORT right_intensity) { return STATUS_NOT_SUPPORTED; } @@ -193,7 +194,8 @@ static void keyboard_stop(struct unix_device *iface) }
static NTSTATUS keyboard_haptics_start(struct unix_device *iface, UINT duration, - USHORT rumble_intensity, USHORT buzz_intensity) + USHORT rumble_intensity, USHORT buzz_intensity, + USHORT left_intensity, USHORT right_intensity) { return STATUS_NOT_SUPPORTED; }
The SDL_HAPTIC_LEFTRIGHT effect usually involves re-starting the effect on every update, possibly causing intermittent rumble waves instead of a continuous effect.
We will also use the newer SDL_JoystickRumbleTriggers function when available, in order to support trigger rumble on the XBox One and DualSense controllers.
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winebus.sys/bus_sdl.c | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index aab629de5da..dbc9ce6a858 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -195,8 +195,7 @@ static BOOL descriptor_add_haptic(struct sdl_device *impl) impl->effect_support |= WINE_SDL_HAPTIC_RUMBLE; }
- if (!(impl->effect_support & EFFECT_SUPPORT_HAPTICS) && pSDL_JoystickRumble && - !pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0)) + if (pSDL_JoystickRumble && !pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0)) impl->effect_support |= WINE_SDL_JOYSTICK_RUMBLE;
if (impl->effect_support & EFFECT_SUPPORT_HAPTICS) @@ -436,21 +435,24 @@ static NTSTATUS sdl_device_haptics_start(struct unix_device *iface, UINT duratio USHORT left_intensity, USHORT right_intensity) { struct sdl_device *impl = impl_from_unix_device(iface); - SDL_HapticEffect effect;
TRACE("iface %p, duration_ms %u, rumble_intensity %u, buzz_intensity %u, left_intensity %u, right_intensity %u.\n", iface, duration_ms, rumble_intensity, buzz_intensity, left_intensity, right_intensity);
if (!(impl->effect_support & EFFECT_SUPPORT_HAPTICS)) return STATUS_NOT_SUPPORTED;
- memset(&effect, 0, sizeof(SDL_HapticEffect)); - effect.type = SDL_HAPTIC_LEFTRIGHT; - effect.leftright.length = duration_ms; - effect.leftright.large_magnitude = rumble_intensity; - effect.leftright.small_magnitude = buzz_intensity; - - if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT) + if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE) + pSDL_JoystickRumble(impl->sdl_joystick, rumble_intensity, buzz_intensity, duration_ms); + else if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT) { + SDL_HapticEffect effect; + + memset(&effect, 0, sizeof(SDL_HapticEffect)); + effect.type = SDL_HAPTIC_LEFTRIGHT; + effect.leftright.length = duration_ms; + effect.leftright.large_magnitude = rumble_intensity; + effect.leftright.small_magnitude = buzz_intensity; + if (impl->haptic_effect_id >= 0) pSDL_HapticDestroyEffect(impl->sdl_haptic, impl->haptic_effect_id); impl->haptic_effect_id = pSDL_HapticNewEffect(impl->sdl_haptic, &effect); @@ -459,13 +461,8 @@ static NTSTATUS sdl_device_haptics_start(struct unix_device *iface, UINT duratio } else if (impl->effect_support & WINE_SDL_HAPTIC_RUMBLE) { - float magnitude = (effect.leftright.large_magnitude + effect.leftright.small_magnitude) / 2.0 / 32767.0; - pSDL_HapticRumblePlay(impl->sdl_haptic, magnitude, effect.leftright.length); - } - else if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE) - { - pSDL_JoystickRumble(impl->sdl_joystick, effect.leftright.large_magnitude, - effect.leftright.small_magnitude, duration_ms); + float magnitude = (rumble_intensity + buzz_intensity) / 2.0 / 32767.0; + pSDL_HapticRumblePlay(impl->sdl_haptic, magnitude, duration_ms); }
return STATUS_SUCCESS; @@ -477,12 +474,12 @@ static NTSTATUS sdl_device_haptics_stop(struct unix_device *iface)
TRACE("iface %p.\n", iface);
- if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT) + if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE) + pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0); + else if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT) pSDL_HapticStopAll(impl->sdl_haptic); else if (impl->effect_support & WINE_SDL_HAPTIC_RUMBLE) pSDL_HapticRumbleStop(impl->sdl_haptic); - else if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE) - pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0);
return STATUS_SUCCESS; }
Signed-off-by: Rémi Bernon rbernon@codeweavers.com --- dlls/winebus.sys/bus_sdl.c | 10 ++++++++++ 1 file changed, 10 insertions(+)
diff --git a/dlls/winebus.sys/bus_sdl.c b/dlls/winebus.sys/bus_sdl.c index dbc9ce6a858..07fdd9d3fa2 100644 --- a/dlls/winebus.sys/bus_sdl.c +++ b/dlls/winebus.sys/bus_sdl.c @@ -114,6 +114,7 @@ MAKE_FUNCPTR(SDL_RegisterEvents); MAKE_FUNCPTR(SDL_PushEvent); MAKE_FUNCPTR(SDL_GetTicks); static int (*pSDL_JoystickRumble)(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms); +static int (*pSDL_JoystickRumbleTriggers)(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble, Uint32 duration_ms); static Uint16 (*pSDL_JoystickGetProduct)(SDL_Joystick * joystick); static Uint16 (*pSDL_JoystickGetProductVersion)(SDL_Joystick * joystick); static Uint16 (*pSDL_JoystickGetVendor)(SDL_Joystick * joystick); @@ -442,7 +443,11 @@ static NTSTATUS sdl_device_haptics_start(struct unix_device *iface, UINT duratio if (!(impl->effect_support & EFFECT_SUPPORT_HAPTICS)) return STATUS_NOT_SUPPORTED;
if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE) + { pSDL_JoystickRumble(impl->sdl_joystick, rumble_intensity, buzz_intensity, duration_ms); + if (pSDL_JoystickRumbleTriggers) + pSDL_JoystickRumbleTriggers(impl->sdl_joystick, left_intensity, right_intensity, duration_ms); + } else if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT) { SDL_HapticEffect effect; @@ -475,7 +480,11 @@ static NTSTATUS sdl_device_haptics_stop(struct unix_device *iface) TRACE("iface %p.\n", iface);
if (impl->effect_support & WINE_SDL_JOYSTICK_RUMBLE) + { pSDL_JoystickRumble(impl->sdl_joystick, 0, 0, 0); + if (pSDL_JoystickRumbleTriggers) + pSDL_JoystickRumbleTriggers(impl->sdl_joystick, 0, 0, 0); + } else if (impl->effect_support & SDL_HAPTIC_LEFTRIGHT) pSDL_HapticStopAll(impl->sdl_haptic); else if (impl->effect_support & WINE_SDL_HAPTIC_RUMBLE) @@ -1050,6 +1059,7 @@ NTSTATUS sdl_bus_init(void *args) LOAD_FUNCPTR(SDL_GetTicks); #undef LOAD_FUNCPTR pSDL_JoystickRumble = dlsym(sdl_handle, "SDL_JoystickRumble"); + pSDL_JoystickRumbleTriggers = dlsym(sdl_handle, "SDL_JoystickRumbleTriggers"); pSDL_JoystickGetProduct = dlsym(sdl_handle, "SDL_JoystickGetProduct"); pSDL_JoystickGetProductVersion = dlsym(sdl_handle, "SDL_JoystickGetProductVersion"); pSDL_JoystickGetVendor = dlsym(sdl_handle, "SDL_JoystickGetVendor");
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); }
[