From cff883a3c823c5d1e86e3f6bc1851c3a62348b70 Mon Sep 17 00:00:00 2001 From: Matt Reynolds Date: Wed, 10 Jan 2018 23:46:39 +0000 Subject: [PATCH] Add haptics for XInput gamepads on Windows BUG=749295 Change-Id: I033a299bbd6c405fdb9fedefd18f36c21d3b8043 Reviewed-on: https://chromium-review.googlesource.com/764428 Commit-Queue: Matt Reynolds Reviewed-by: Brandon Jones Cr-Commit-Position: refs/heads/master@{#528488} --- device/gamepad/BUILD.gn | 2 + .../gamepad_platform_data_fetcher_win.cc | 64 +++++++++++++++++-- .../gamepad_platform_data_fetcher_win.h | 18 +++++- device/gamepad/xinput_haptic_gamepad_win.cc | 35 ++++++++++ device/gamepad/xinput_haptic_gamepad_win.h | 32 ++++++++++ 5 files changed, 144 insertions(+), 7 deletions(-) create mode 100644 device/gamepad/xinput_haptic_gamepad_win.cc create mode 100644 device/gamepad/xinput_haptic_gamepad_win.h diff --git a/device/gamepad/BUILD.gn b/device/gamepad/BUILD.gn index 3da4be00c3a398..96a18d75aa7c8b 100644 --- a/device/gamepad/BUILD.gn +++ b/device/gamepad/BUILD.gn @@ -66,6 +66,8 @@ component("gamepad") { "xbox_controller_mac.mm", "xbox_data_fetcher_mac.cc", "xbox_data_fetcher_mac.h", + "xinput_haptic_gamepad_win.cc", + "xinput_haptic_gamepad_win.h", ] deps = [ diff --git a/device/gamepad/gamepad_platform_data_fetcher_win.cc b/device/gamepad/gamepad_platform_data_fetcher_win.cc index 8aa7d892eef98a..5466399bd8a621 100644 --- a/device/gamepad/gamepad_platform_data_fetcher_win.cc +++ b/device/gamepad/gamepad_platform_data_fetcher_win.cc @@ -87,8 +87,7 @@ const UChar* XInputDllFileName() { GamepadPlatformDataFetcherWin::GamepadPlatformDataFetcherWin() : xinput_available_(false) {} -GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() { -} +GamepadPlatformDataFetcherWin::~GamepadPlatformDataFetcherWin() = default; GamepadSource GamepadPlatformDataFetcherWin::source() { return Factory::static_source(); @@ -108,9 +107,11 @@ void GamepadPlatformDataFetcherWin::EnumerateDevices() { // Check to see if the xinput device is connected XINPUT_CAPABILITIES caps; DWORD res = xinput_get_capabilities_(i, XINPUT_FLAG_GAMEPAD, &caps); - xinuput_connected_[i] = (res == ERROR_SUCCESS); - if (!xinuput_connected_[i]) + xinput_connected_[i] = (res == ERROR_SUCCESS); + if (!xinput_connected_[i]) { + haptics_[i] = nullptr; continue; + } PadState* state = GetPadState(i); if (!state) @@ -119,9 +120,16 @@ void GamepadPlatformDataFetcherWin::EnumerateDevices() { Gamepad& pad = state->data; if (state->active_state == GAMEPAD_NEWLY_ACTIVE) { + haptics_[i] = + std::make_unique(i, xinput_set_state_); + // This is the first time we've seen this device, so do some one-time // initialization pad.connected = true; + + pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble; + pad.vibration_actuator.not_null = true; + swprintf(pad.id, Gamepad::kIdLengthCap, L"Xbox 360 Controller (XInput STANDARD %ls)", GamepadSubTypeName(caps.SubType)); @@ -148,7 +156,7 @@ void GamepadPlatformDataFetcherWin::GetGamepadData(bool devices_changed_hint) { EnumerateDevices(); for (size_t i = 0; i < XUSER_MAX_COUNT; ++i) { - if (xinuput_connected_[i]) + if (xinput_connected_[i]) GetXInputPadData(i); } } @@ -227,10 +235,51 @@ void GamepadPlatformDataFetcherWin::GetXInputPadData(int i) { } } +void GamepadPlatformDataFetcherWin::PlayEffect( + int pad_id, + mojom::GamepadHapticEffectType type, + mojom::GamepadEffectParametersPtr params, + mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback) { + if (pad_id < 0 || pad_id >= XUSER_MAX_COUNT) { + std::move(callback).Run( + mojom::GamepadHapticsResult::GamepadHapticsResultError); + return; + } + + if (!xinput_available_ || !xinput_connected_[pad_id] || + haptics_[pad_id] == nullptr) { + std::move(callback).Run( + mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported); + return; + } + + haptics_[pad_id]->PlayEffect(type, std::move(params), std::move(callback)); +} + +void GamepadPlatformDataFetcherWin::ResetVibration( + int pad_id, + mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback) { + if (pad_id < 0 || pad_id >= XUSER_MAX_COUNT) { + std::move(callback).Run( + mojom::GamepadHapticsResult::GamepadHapticsResultError); + return; + } + + if (!xinput_available_ || !xinput_connected_[pad_id] || + haptics_[pad_id] == nullptr) { + std::move(callback).Run( + mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported); + return; + } + + haptics_[pad_id]->ResetVibration(std::move(callback)); +} + bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() { xinput_get_capabilities_ = nullptr; xinput_get_state_ = nullptr; xinput_get_state_ex_ = nullptr; + xinput_set_state_ = nullptr; XInputEnableFunc xinput_enable = reinterpret_cast( xinput_dll_.GetFunctionPointer("XInputEnable")); xinput_get_capabilities_ = reinterpret_cast( @@ -249,6 +298,11 @@ bool GamepadPlatformDataFetcherWin::GetXInputDllFunctions() { if (!xinput_get_state_ && !xinput_get_state_ex_) return false; + xinput_set_state_ = + reinterpret_cast( + xinput_dll_.GetFunctionPointer("XInputSetState")); + if (!xinput_set_state_) + return false; if (xinput_enable) { // XInputEnable is unavailable before Win8 and deprecated in Win10. xinput_enable(true); diff --git a/device/gamepad/gamepad_platform_data_fetcher_win.h b/device/gamepad/gamepad_platform_data_fetcher_win.h index e6f16797df314b..9925ca3dcec178 100644 --- a/device/gamepad/gamepad_platform_data_fetcher_win.h +++ b/device/gamepad/gamepad_platform_data_fetcher_win.h @@ -26,6 +26,7 @@ #include "device/gamepad/gamepad_data_fetcher.h" #include "device/gamepad/gamepad_standard_mappings.h" #include "device/gamepad/public/cpp/gamepads.h" +#include "device/gamepad/xinput_haptic_gamepad_win.h" namespace device { @@ -57,8 +58,19 @@ class GamepadPlatformDataFetcherWin : public GamepadDataFetcher { GamepadSource source() override; + // GamepadDataFetcher implementation. void GetGamepadData(bool devices_changed_hint) override; + void PlayEffect( + int pad_index, + mojom::GamepadHapticEffectType, + mojom::GamepadEffectParametersPtr, + mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback) override; + + void ResetVibration( + int pad_index, + mojom::GamepadHapticsManager::ResetVibrationActuatorCallback) override; + private: void OnAddedToProvider() override; @@ -85,12 +97,14 @@ class GamepadPlatformDataFetcherWin : public GamepadDataFetcher { bool xinput_available_; // Function pointers to XInput functionality, retrieved in - // |GetXinputDllFunctions|. + // |GetXInputDllFunctions|. XInputGetCapabilitiesFunc xinput_get_capabilities_; XInputGetStateFunc xinput_get_state_; XInputGetStateExFunc xinput_get_state_ex_; + XInputHapticGamepadWin::XInputSetStateFunc xinput_set_state_; - bool xinuput_connected_[XUSER_MAX_COUNT]; + bool xinput_connected_[XUSER_MAX_COUNT]; + std::unique_ptr haptics_[XUSER_MAX_COUNT]; DISALLOW_COPY_AND_ASSIGN(GamepadPlatformDataFetcherWin); }; diff --git a/device/gamepad/xinput_haptic_gamepad_win.cc b/device/gamepad/xinput_haptic_gamepad_win.cc new file mode 100644 index 00000000000000..cf9076e47e82a9 --- /dev/null +++ b/device/gamepad/xinput_haptic_gamepad_win.cc @@ -0,0 +1,35 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "device/gamepad/xinput_haptic_gamepad_win.h" + +namespace { +const long kRumbleMagnitudeMax = 0xffff; +} // namespace + +namespace device { + +XInputHapticGamepadWin::XInputHapticGamepadWin( + int pad_id, + XInputSetStateFunc xinput_set_state) + : pad_id_(pad_id), xinput_set_state_(xinput_set_state) {} + +XInputHapticGamepadWin::~XInputHapticGamepadWin() = default; + +void XInputHapticGamepadWin::SetVibration(double strong_magnitude, + double weak_magnitude) { + if (pad_id_ < 0 || pad_id_ > XUSER_MAX_COUNT || xinput_set_state_ == nullptr) + return; + XINPUT_VIBRATION vibration; + vibration.wLeftMotorSpeed = + static_cast(strong_magnitude * kRumbleMagnitudeMax); + vibration.wRightMotorSpeed = + static_cast(weak_magnitude * kRumbleMagnitudeMax); + + TRACE_EVENT_BEGIN1("GAMEPAD", "XInputSetState", "id", pad_id_); + xinput_set_state_(pad_id_, &vibration); + TRACE_EVENT_END1("GAMEPAD", "XInputSetState", "id", pad_id_); +} + +} // namespace device diff --git a/device/gamepad/xinput_haptic_gamepad_win.h b/device/gamepad/xinput_haptic_gamepad_win.h new file mode 100644 index 00000000000000..9abfbfc1201879 --- /dev/null +++ b/device/gamepad/xinput_haptic_gamepad_win.h @@ -0,0 +1,32 @@ +// Copyright 2018 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef DEVICE_GAMEPAD_XINPUT_HAPTIC_GAMEPAD_WIN_ +#define DEVICE_GAMEPAD_XINPUT_HAPTIC_GAMEPAD_WIN_ + +#include +#include + +#include "device/gamepad/abstract_haptic_gamepad.h" + +namespace device { + +class XInputHapticGamepadWin : public AbstractHapticGamepad { + public: + typedef DWORD(WINAPI* XInputSetStateFunc)(DWORD dwUserIndex, + XINPUT_VIBRATION* pVibration); + + XInputHapticGamepadWin(int pad_id, XInputSetStateFunc xinput_set_state); + ~XInputHapticGamepadWin() override; + + void SetVibration(double strong_magnitude, double weak_magnitude) override; + + private: + int pad_id_; + XInputSetStateFunc xinput_set_state_; +}; + +} // namespace device + +#endif // DEVICE_GAMEPAD_EVDEV_HAPTIC_GAMEPAD_WIN_