[Gamepad] Implement dual-rumble effect for WGI gamepads
This CL implements the dual-rumble effect for Windows.Gaming.Input (WGI) gamepads. It does so by making WgiGamepadDevice inherit from AbstractHapticGamepad and by making every WGI gamepad object to have a 1: 1 association with a WgiGamepadDevice object. Change-Id: I67284a3a2c8be0d78e71c59b667836291a03d11d Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/3347270 Reviewed-by: Matt Reynolds <mattreynolds@chromium.org> Commit-Queue: Gabriel Brito <gabrielbrito@microsoft.com> Cr-Commit-Position: refs/heads/main@{#956500}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
f03f9a3c09
commit
0b7e2c62ad
@ -81,6 +81,8 @@ component("gamepad") {
|
||||
"raw_input_gamepad_device_win.h",
|
||||
"wgi_data_fetcher_win.cc",
|
||||
"wgi_data_fetcher_win.h",
|
||||
"wgi_gamepad_device.cc",
|
||||
"wgi_gamepad_device.h",
|
||||
"xinput_data_fetcher_win.cc",
|
||||
"xinput_data_fetcher_win.h",
|
||||
"xinput_haptic_gamepad_win.cc",
|
||||
|
@ -24,14 +24,17 @@ FakeIGamepad::~FakeIGamepad() = default;
|
||||
|
||||
HRESULT WINAPI FakeIGamepad::get_Vibration(
|
||||
ABI::Windows::Gaming::Input::GamepadVibration* gamepad_vibration) {
|
||||
NOTIMPLEMENTED();
|
||||
return E_NOTIMPL;
|
||||
gamepad_vibration->LeftMotor = fake_gamepad_vibration_.LeftMotor;
|
||||
gamepad_vibration->LeftTrigger = fake_gamepad_vibration_.LeftTrigger;
|
||||
gamepad_vibration->RightMotor = fake_gamepad_vibration_.RightMotor;
|
||||
gamepad_vibration->RightTrigger = fake_gamepad_vibration_.RightTrigger;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WINAPI FakeIGamepad::put_Vibration(
|
||||
ABI::Windows::Gaming::Input::GamepadVibration gamepad_vibration) {
|
||||
NOTIMPLEMENTED();
|
||||
return E_NOTIMPL;
|
||||
fake_gamepad_vibration_ = gamepad_vibration;
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT WINAPI FakeIGamepad::GetCurrentReading(
|
||||
|
@ -77,6 +77,7 @@ class FakeIGamepad final
|
||||
bool has_paddles_ = false;
|
||||
|
||||
ABI::Windows::Gaming::Input::GamepadReading fake_gamepad_reading_;
|
||||
ABI::Windows::Gaming::Input::GamepadVibration fake_gamepad_vibration_;
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
|
@ -7,10 +7,12 @@
|
||||
#include <stdint.h>
|
||||
#include <wrl/event.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/cxx20_erase.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "base/strings/string_util_win.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
@ -23,6 +25,8 @@
|
||||
#include "device/gamepad/gamepad_id_list.h"
|
||||
#include "device/gamepad/gamepad_standard_mappings.h"
|
||||
#include "device/gamepad/nintendo_controller.h"
|
||||
#include "device/gamepad/wgi_gamepad_device.h"
|
||||
|
||||
namespace device {
|
||||
|
||||
namespace {
|
||||
@ -150,6 +154,11 @@ WgiDataFetcherWin::WgiDataFetcherWin() {
|
||||
|
||||
WgiDataFetcherWin::~WgiDataFetcherWin() {
|
||||
UnregisterEventHandlers();
|
||||
for (auto& map_entry : devices_) {
|
||||
if (map_entry.second) {
|
||||
map_entry.second->Shutdown();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GamepadSource WgiDataFetcherWin::source() {
|
||||
@ -211,7 +220,7 @@ void WgiDataFetcherWin::OnGamepadAdded(
|
||||
// gamepad polling thread when the callback is returned on a different thread
|
||||
// from the IGamepadStatics COM API. Thus `OnGamepadAdded` is also running on
|
||||
// gamepad polling thread, it is the only thread that is able to access the
|
||||
// `gamepads_` object, making it thread-safe.
|
||||
// `devices_` object, making it thread-safe.
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
if (initialization_state_ != InitializationState::kInitialized)
|
||||
@ -232,9 +241,9 @@ void WgiDataFetcherWin::OnGamepadAdded(
|
||||
pad.SetID(display_name);
|
||||
pad.connected = true;
|
||||
pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble;
|
||||
pad.vibration_actuator.not_null = false;
|
||||
pad.vibration_actuator.not_null = true;
|
||||
pad.mapping = GamepadMapping::kStandard;
|
||||
gamepads_.push_back({source_id, gamepad});
|
||||
devices_[source_id] = std::make_unique<WgiGamepadDevice>(gamepad);
|
||||
}
|
||||
|
||||
void WgiDataFetcherWin::OnGamepadRemoved(
|
||||
@ -245,26 +254,29 @@ void WgiDataFetcherWin::OnGamepadRemoved(
|
||||
// gamepad polling thread when the callback is returned on a different thread
|
||||
// from the IGamepadStatics COM API. Thus `OnGamepadRemoved` is also running
|
||||
// on gamepad polling thread, it is the only thread that is able to access the
|
||||
// `gamepads_` object, making it thread-safe.
|
||||
// `devices_` object, making it thread-safe.
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
DCHECK_EQ(initialization_state_, InitializationState::kInitialized);
|
||||
|
||||
base::EraseIf(gamepads_,
|
||||
[=](const WindowsGamingInputControllerMapping& mapping) {
|
||||
return mapping.gamepad.Get() == gamepad;
|
||||
});
|
||||
base::EraseIf(devices_, [=](const auto& map_entry) {
|
||||
if (map_entry.second->GetGamepad().Get() == gamepad) {
|
||||
map_entry.second->Shutdown();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
void WgiDataFetcherWin::GetGamepadData(bool devices_changed_hint) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
for (const auto& gamepad_mapping : gamepads_) {
|
||||
PadState* state = GetPadState(gamepad_mapping.source_id);
|
||||
for (const auto& map_entry : devices_) {
|
||||
PadState* state = GetPadState(map_entry.first);
|
||||
if (!state)
|
||||
continue;
|
||||
|
||||
ABI::Windows::Gaming::Input::GamepadReading reading;
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad =
|
||||
gamepad_mapping.gamepad;
|
||||
map_entry.second->GetGamepad();
|
||||
if (FAILED(gamepad->GetCurrentReading(&reading)))
|
||||
continue;
|
||||
|
||||
@ -345,6 +357,44 @@ void WgiDataFetcherWin::GetGamepadData(bool devices_changed_hint) {
|
||||
}
|
||||
}
|
||||
|
||||
void WgiDataFetcherWin::PlayEffect(
|
||||
int source_id,
|
||||
mojom::GamepadHapticEffectType type,
|
||||
mojom::GamepadEffectParametersPtr params,
|
||||
mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback callback,
|
||||
scoped_refptr<base::SequencedTaskRunner> callback_runner) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto map_entry = devices_.find(source_id);
|
||||
if (map_entry == devices_.end()) {
|
||||
RunVibrationCallback(
|
||||
std::move(callback), std::move(callback_runner),
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
map_entry->second->PlayEffect(type, std::move(params), std::move(callback),
|
||||
std::move(callback_runner));
|
||||
}
|
||||
|
||||
void WgiDataFetcherWin::ResetVibration(
|
||||
int source_id,
|
||||
mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback,
|
||||
scoped_refptr<base::SequencedTaskRunner> callback_runner) {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
|
||||
auto map_entry = devices_.find(source_id);
|
||||
if (map_entry == devices_.end()) {
|
||||
RunVibrationCallback(
|
||||
std::move(callback), std::move(callback_runner),
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
|
||||
return;
|
||||
}
|
||||
|
||||
map_entry->second->ResetVibration(std::move(callback),
|
||||
std::move(callback_runner));
|
||||
}
|
||||
|
||||
// static
|
||||
void WgiDataFetcherWin::OverrideActivationFactoryFunctionForTesting(
|
||||
WgiDataFetcherWin::ActivationFactoryFunctionCallback callback) {
|
||||
@ -360,11 +410,6 @@ WgiDataFetcherWin::GetActivationFactoryFunctionCallback() {
|
||||
return *instance;
|
||||
}
|
||||
|
||||
const std::vector<WgiDataFetcherWin::WindowsGamingInputControllerMapping>&
|
||||
WgiDataFetcherWin::GetGamepadsForTesting() const {
|
||||
return gamepads_;
|
||||
}
|
||||
|
||||
WgiDataFetcherWin::InitializationState
|
||||
WgiDataFetcherWin::GetInitializationState() const {
|
||||
return initialization_state_;
|
||||
@ -416,34 +461,4 @@ void WgiDataFetcherWin::UnregisterEventHandlers() {
|
||||
}
|
||||
}
|
||||
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping::
|
||||
WindowsGamingInputControllerMapping(
|
||||
int input_source_id,
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad>
|
||||
input_gamepad)
|
||||
: source_id(input_source_id), gamepad(input_gamepad) {}
|
||||
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping::
|
||||
WindowsGamingInputControllerMapping(
|
||||
const WgiDataFetcherWin::WindowsGamingInputControllerMapping& other) =
|
||||
default;
|
||||
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping&
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping::
|
||||
WindowsGamingInputControllerMapping::operator=(
|
||||
const WgiDataFetcherWin::WindowsGamingInputControllerMapping& other) =
|
||||
default;
|
||||
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping::
|
||||
WindowsGamingInputControllerMapping(
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping&& other) =
|
||||
default;
|
||||
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping&
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping::operator=(
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping&&) = default;
|
||||
|
||||
WgiDataFetcherWin::WindowsGamingInputControllerMapping::
|
||||
~WindowsGamingInputControllerMapping() = default;
|
||||
|
||||
} // namespace device
|
||||
|
@ -10,9 +10,16 @@
|
||||
#include <Windows.Gaming.Input.h>
|
||||
#include <wrl/event.h>
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/memory/scoped_refptr.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "device/gamepad/public/mojom/gamepad.mojom.h"
|
||||
#include "device/gamepad/wgi_gamepad_device.h"
|
||||
#include "third_party/abseil-cpp/absl/types/optional.h"
|
||||
|
||||
namespace device {
|
||||
@ -20,31 +27,6 @@ namespace device {
|
||||
class DEVICE_GAMEPAD_EXPORT WgiDataFetcherWin final
|
||||
: public GamepadDataFetcher {
|
||||
public:
|
||||
struct WindowsGamingInputControllerMapping {
|
||||
public:
|
||||
WindowsGamingInputControllerMapping(
|
||||
int input_source_id,
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad>
|
||||
input_gamepad);
|
||||
|
||||
WindowsGamingInputControllerMapping(
|
||||
const WindowsGamingInputControllerMapping& other);
|
||||
|
||||
WindowsGamingInputControllerMapping(
|
||||
WindowsGamingInputControllerMapping&& other);
|
||||
|
||||
WindowsGamingInputControllerMapping& operator=(
|
||||
const WindowsGamingInputControllerMapping& other);
|
||||
|
||||
WindowsGamingInputControllerMapping& operator=(
|
||||
WindowsGamingInputControllerMapping&&);
|
||||
|
||||
~WindowsGamingInputControllerMapping();
|
||||
|
||||
int source_id;
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad;
|
||||
};
|
||||
|
||||
enum class InitializationState {
|
||||
kUninitialized,
|
||||
kInitialized,
|
||||
@ -72,6 +54,15 @@ class DEVICE_GAMEPAD_EXPORT WgiDataFetcherWin final
|
||||
GamepadSource source() override;
|
||||
void OnAddedToProvider() override;
|
||||
void GetGamepadData(bool devices_changed_hint) override;
|
||||
void PlayEffect(int source_id,
|
||||
mojom::GamepadHapticEffectType,
|
||||
mojom::GamepadEffectParametersPtr,
|
||||
mojom::GamepadHapticsManager::PlayVibrationEffectOnceCallback,
|
||||
scoped_refptr<base::SequencedTaskRunner>) override;
|
||||
void ResetVibration(
|
||||
int source_id,
|
||||
mojom::GamepadHapticsManager::ResetVibrationActuatorCallback,
|
||||
scoped_refptr<base::SequencedTaskRunner>) override;
|
||||
|
||||
// Set fake ActivationFunction for test to avoid dependencies on the OS API.
|
||||
using ActivationFactoryFunctionCallback =
|
||||
@ -79,8 +70,9 @@ class DEVICE_GAMEPAD_EXPORT WgiDataFetcherWin final
|
||||
static void OverrideActivationFactoryFunctionForTesting(
|
||||
ActivationFactoryFunctionCallback callback);
|
||||
|
||||
const std::vector<WindowsGamingInputControllerMapping>&
|
||||
GetGamepadsForTesting() const;
|
||||
// Used to store gamepad devices indexed by its source id.
|
||||
using DeviceMap = base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>;
|
||||
const DeviceMap& GetGamepadsForTesting() const { return devices_; }
|
||||
|
||||
InitializationState GetInitializationState() const;
|
||||
|
||||
@ -111,7 +103,7 @@ class DEVICE_GAMEPAD_EXPORT WgiDataFetcherWin final
|
||||
InitializationState initialization_state_ =
|
||||
InitializationState::kUninitialized;
|
||||
|
||||
std::vector<WindowsGamingInputControllerMapping> gamepads_;
|
||||
DeviceMap devices_;
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepadStatics>
|
||||
gamepad_statics_;
|
||||
|
@ -4,7 +4,9 @@
|
||||
|
||||
#include "device/gamepad/wgi_data_fetcher_win.h"
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/containers/fixed_flat_map.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/shared_memory_mapping.h"
|
||||
#include "base/run_loop.h"
|
||||
@ -18,10 +20,13 @@
|
||||
#include "device/gamepad/gamepad_provider.h"
|
||||
#include "device/gamepad/gamepad_standard_mappings.h"
|
||||
#include "device/gamepad/public/cpp/gamepad.h"
|
||||
#include "device/gamepad/public/mojom/gamepad.mojom.h"
|
||||
#include "device/gamepad/public/mojom/gamepad_hardware_buffer.h"
|
||||
#include "device/gamepad/test_support/fake_igamepad.h"
|
||||
#include "device/gamepad/test_support/fake_igamepad_statics.h"
|
||||
#include "device/gamepad/test_support/fake_winrt_wgi_environment.h"
|
||||
#include "device/gamepad/wgi_data_fetcher_win.h"
|
||||
#include "device/gamepad/wgi_gamepad_device.h"
|
||||
#include "services/device/device_service_test_base.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
|
||||
@ -42,6 +47,11 @@ constexpr unsigned int kGamepadButtonsLength = 16;
|
||||
constexpr unsigned int kGamepadWithPaddlesButtonsLength = 21;
|
||||
constexpr unsigned int kGamepadAxesLength = 4;
|
||||
|
||||
constexpr double kDurationMillis = 1.0;
|
||||
constexpr double kZeroStartDelayMillis = 0.0;
|
||||
constexpr double kStrongMagnitude = 1.0; // 100% intensity.
|
||||
constexpr double kWeakMagnitude = 0.5; // 50% intensity.
|
||||
|
||||
constexpr ErrorCode kErrors[] = {
|
||||
ErrorCode::kErrorWgiRawGameControllerActivateFailed,
|
||||
ErrorCode::kErrorWgiRawGameControllerFromGameControllerFailed,
|
||||
@ -140,7 +150,7 @@ class WgiDataFetcherWinTest : public DeviceServiceTestBase {
|
||||
EXPECT_TRUE(pad_state->is_initialized);
|
||||
Gamepad& pad = pad_state->data;
|
||||
EXPECT_TRUE(pad.connected);
|
||||
EXPECT_FALSE(pad.vibration_actuator.not_null);
|
||||
EXPECT_TRUE(pad.vibration_actuator.not_null);
|
||||
}
|
||||
|
||||
void CheckGamepadRemoved() {
|
||||
@ -286,7 +296,41 @@ class WgiDataFetcherWinTest : public DeviceServiceTestBase {
|
||||
|
||||
WgiDataFetcherWin& fetcher() const { return *fetcher_; }
|
||||
|
||||
// Gets called after PlayEffect or ResetVibration.
|
||||
void HapticsCallback(mojom::GamepadHapticsResult result) {
|
||||
haptics_callback_count_++;
|
||||
haptics_callback_result_ = result;
|
||||
}
|
||||
|
||||
void SimulateDualRumbleEffect(int pad_index) {
|
||||
base::RunLoop run_loop;
|
||||
provider_->PlayVibrationEffectOnce(
|
||||
pad_index,
|
||||
mojom::GamepadHapticEffectType::GamepadHapticEffectTypeDualRumble,
|
||||
mojom::GamepadEffectParameters::New(kDurationMillis,
|
||||
kZeroStartDelayMillis,
|
||||
kStrongMagnitude, kWeakMagnitude),
|
||||
base::BindOnce(&WgiDataFetcherWinTest::HapticsCallback,
|
||||
base::Unretained(this))
|
||||
.Then(run_loop.QuitClosure()));
|
||||
FlushPollingThread();
|
||||
run_loop.Run();
|
||||
}
|
||||
|
||||
void SimulateResetVibration(int pad_index) {
|
||||
base::RunLoop run_loop;
|
||||
provider_->ResetVibrationActuator(
|
||||
pad_index, base::BindOnce(&WgiDataFetcherWinTest::HapticsCallback,
|
||||
base::Unretained(this))
|
||||
.Then(run_loop.QuitClosure()));
|
||||
FlushPollingThread();
|
||||
run_loop.Run();
|
||||
}
|
||||
|
||||
protected:
|
||||
int haptics_callback_count_ = 0;
|
||||
mojom::GamepadHapticsResult haptics_callback_result_ =
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultError;
|
||||
std::unique_ptr<GamepadProvider> provider_;
|
||||
std::unique_ptr<FakeWinrtWgiEnvironment> wgi_environment_;
|
||||
|
||||
@ -300,7 +344,7 @@ TEST_F(WgiDataFetcherWinTest, AddAndRemoveWgiGamepad) {
|
||||
|
||||
// Check initial number of connected gamepad and WGI initialization status.
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
EXPECT_TRUE(fetcher().GetGamepadsForTesting().empty());
|
||||
|
||||
const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>();
|
||||
@ -321,10 +365,10 @@ TEST_F(WgiDataFetcherWinTest, AddAndRemoveWgiGamepad) {
|
||||
FlushPollingThread();
|
||||
|
||||
// Assert that the gamepad has been added to the DataFetcher.
|
||||
const std::vector<WgiDataFetcherWin::WindowsGamingInputControllerMapping>&
|
||||
gamepads = fetcher().GetGamepadsForTesting();
|
||||
const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads =
|
||||
fetcher().GetGamepadsForTesting();
|
||||
ASSERT_EQ(gamepads.size(), 1u);
|
||||
CheckGamepadAdded(fetcher().GetPadState(gamepads.front().source_id));
|
||||
CheckGamepadAdded(fetcher().GetPadState(gamepads.begin()->first));
|
||||
|
||||
// Simulate the gamepad removing behavior, and make the gamepad-removing
|
||||
// callback return on a different thread, demonstrated the multi-threaded
|
||||
@ -344,9 +388,8 @@ TEST_F(WgiDataFetcherWinTest, AddGamepadAddedEventHandlerErrorHandling) {
|
||||
SetUpTestEnv(ErrorCode::kGamepadAddGamepadAddedFailed);
|
||||
|
||||
// Check WGI initialization status.
|
||||
EXPECT_EQ(
|
||||
fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::kAddGamepadAddedFailed);
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
WgiDataFetcherWin::InitializationState::kAddGamepadAddedFailed);
|
||||
auto* gamepad_statics = FakeIGamepadStatics::GetInstance();
|
||||
EXPECT_EQ(gamepad_statics->GetGamepadAddedEventHandlerCount(), 0u);
|
||||
}
|
||||
@ -357,9 +400,8 @@ TEST_F(WgiDataFetcherWinTest, AddGamepadRemovedEventHandlerErrorHandling) {
|
||||
SetUpTestEnv(ErrorCode::kGamepadAddGamepadRemovedFailed);
|
||||
|
||||
// Check WGI initialization status.
|
||||
EXPECT_EQ(
|
||||
fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::kAddGamepadRemovedFailed);
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
WgiDataFetcherWin::InitializationState::kAddGamepadRemovedFailed);
|
||||
auto* gamepad_statics = FakeIGamepadStatics::GetInstance();
|
||||
EXPECT_EQ(gamepad_statics->GetGamepadRemovedEventHandlerCount(), 0u);
|
||||
}
|
||||
@ -370,9 +412,9 @@ TEST_F(WgiDataFetcherWinTest, WgiGamepadActivationFactoryErrorHandling) {
|
||||
SetUpTestEnv(ErrorCode::kErrorWgiGamepadActivateFailed);
|
||||
|
||||
// Check WGI initialization status.
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::
|
||||
kRoGetActivationFactoryFailed);
|
||||
EXPECT_EQ(
|
||||
fetcher().GetInitializationState(),
|
||||
WgiDataFetcherWin::InitializationState::kRoGetActivationFactoryFailed);
|
||||
}
|
||||
|
||||
// If RawGameController2::get_DisplayName fails when calling
|
||||
@ -392,10 +434,10 @@ TEST_F(WgiDataFetcherWinTest, FailuretoGetDisplayNameOnGamepadAdded) {
|
||||
FlushPollingThread();
|
||||
|
||||
// Assert that the gamepad has not been added to the DataFetcher.
|
||||
const std::vector<WgiDataFetcherWin::WindowsGamingInputControllerMapping>&
|
||||
gamepads = fetcher().GetGamepadsForTesting();
|
||||
const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads =
|
||||
fetcher().GetGamepadsForTesting();
|
||||
ASSERT_EQ(gamepads.size(), 1u);
|
||||
PadState* pad = fetcher().GetPadState(gamepads.front().source_id);
|
||||
PadState* pad = fetcher().GetPadState(gamepads.begin()->first);
|
||||
std::u16string display_id(pad->data.id);
|
||||
EXPECT_EQ(kDefaultDisplayName, display_id);
|
||||
CheckGamepadAdded(pad);
|
||||
@ -406,7 +448,7 @@ TEST_F(WgiDataFetcherWinTest, VerifyGamepadInput) {
|
||||
|
||||
// Check initial number of connected gamepad and WGI initialization status.
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
|
||||
auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance();
|
||||
const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>();
|
||||
@ -449,6 +491,55 @@ TEST_F(WgiDataFetcherWinTest, VerifyGamepadInput) {
|
||||
/*has_paddles*/ true);
|
||||
}
|
||||
|
||||
TEST_F(WgiDataFetcherWinTest, PlayDualRumbleEffect) {
|
||||
SetUpTestEnv();
|
||||
// Check initial number of connected gamepad and WGI initialization status.
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
|
||||
auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance();
|
||||
const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>();
|
||||
|
||||
// Add a simulated WGI device.
|
||||
provider_->Resume();
|
||||
fake_gamepad_statics->SimulateGamepadAdded(
|
||||
fake_gamepad, kHardwareProductId, kHardwareVendorId, kGamepadDisplayName);
|
||||
|
||||
SimulateDualRumbleEffect(/*pad_index=*/0);
|
||||
EXPECT_EQ(haptics_callback_count_, 1);
|
||||
EXPECT_EQ(haptics_callback_result_,
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultComplete);
|
||||
ABI::Windows::Gaming::Input::GamepadVibration fake_gamepad_vibration;
|
||||
fake_gamepad->get_Vibration(&fake_gamepad_vibration);
|
||||
EXPECT_EQ(fake_gamepad_vibration.LeftMotor, kStrongMagnitude);
|
||||
EXPECT_EQ(fake_gamepad_vibration.RightMotor, kWeakMagnitude);
|
||||
EXPECT_EQ(fake_gamepad_vibration.LeftTrigger, 0.0f);
|
||||
EXPECT_EQ(fake_gamepad_vibration.RightTrigger, 0.0f);
|
||||
|
||||
// Calling ResetVibration sets the vibration intensity to 0 for both motors.
|
||||
SimulateResetVibration(/*pad_index=*/0);
|
||||
EXPECT_EQ(haptics_callback_count_, 2);
|
||||
EXPECT_EQ(haptics_callback_result_,
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultComplete);
|
||||
fake_gamepad->get_Vibration(&fake_gamepad_vibration);
|
||||
EXPECT_EQ(fake_gamepad_vibration.LeftMotor, 0.0f);
|
||||
EXPECT_EQ(fake_gamepad_vibration.RightMotor, 0.0f);
|
||||
EXPECT_EQ(fake_gamepad_vibration.LeftTrigger, 0.0f);
|
||||
EXPECT_EQ(fake_gamepad_vibration.RightTrigger, 0.0f);
|
||||
|
||||
// Attempting to call haptics methods on invalid pad_id's will return a result
|
||||
// of type GamepadHapticsResultNotSupported.
|
||||
fake_gamepad_statics->SimulateGamepadRemoved(fake_gamepad);
|
||||
SimulateDualRumbleEffect(/*pad_index=*/0);
|
||||
EXPECT_EQ(haptics_callback_count_, 3);
|
||||
EXPECT_EQ(haptics_callback_result_,
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
|
||||
SimulateResetVibration(/*pad_index=*/0);
|
||||
EXPECT_EQ(haptics_callback_count_, 4);
|
||||
EXPECT_EQ(haptics_callback_result_,
|
||||
mojom::GamepadHapticsResult::GamepadHapticsResultNotSupported);
|
||||
}
|
||||
|
||||
// When an error happens when calling GamepadGetCurrentReading, the state in
|
||||
// the shared buffer will not be modified.
|
||||
TEST_F(WgiDataFetcherWinTest, WgiGamepadGetCurrentReadingError) {
|
||||
@ -456,7 +547,7 @@ TEST_F(WgiDataFetcherWinTest, WgiGamepadGetCurrentReadingError) {
|
||||
|
||||
// Check initial number of connected gamepad and WGI initialization status.
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
|
||||
auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance();
|
||||
const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>();
|
||||
@ -502,7 +593,7 @@ TEST_F(WgiDataFetcherWinTest, WgiGamepadGetButtonLabelError) {
|
||||
|
||||
// Check initial number of connected gamepad and WGI initialization status.
|
||||
EXPECT_EQ(fetcher().GetInitializationState(),
|
||||
device::WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
WgiDataFetcherWin::InitializationState::kInitialized);
|
||||
|
||||
auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance();
|
||||
const auto fake_gamepad_with_paddles = Microsoft::WRL::Make<FakeIGamepad>();
|
||||
@ -564,8 +655,8 @@ TEST_F(WgiDataFetcherWinTest, ShouldNotEnumerateControllers) {
|
||||
FlushPollingThread();
|
||||
|
||||
// Assert that the gamepad has not been added to the DataFetcher.
|
||||
const std::vector<WgiDataFetcherWin::WindowsGamingInputControllerMapping>&
|
||||
gamepads = fetcher().GetGamepadsForTesting();
|
||||
const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads =
|
||||
fetcher().GetGamepadsForTesting();
|
||||
EXPECT_EQ(gamepads.size(), 0u);
|
||||
}
|
||||
|
||||
@ -589,8 +680,8 @@ TEST_P(WgiDataFetcherWinErrorTest, GamepadShouldNotbeEnumerated) {
|
||||
FlushPollingThread();
|
||||
|
||||
// Assert that the gamepad has not been added to the DataFetcher.
|
||||
const std::vector<WgiDataFetcherWin::WindowsGamingInputControllerMapping>&
|
||||
gamepads = fetcher().GetGamepadsForTesting();
|
||||
const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads =
|
||||
fetcher().GetGamepadsForTesting();
|
||||
EXPECT_EQ(gamepads.size(), 0u);
|
||||
}
|
||||
INSTANTIATE_TEST_SUITE_P(WgiDataFetcherWinErrorTests,
|
||||
|
31
device/gamepad/wgi_gamepad_device.cc
Normal file
31
device/gamepad/wgi_gamepad_device.cc
Normal file
@ -0,0 +1,31 @@
|
||||
// Copyright 2022 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/wgi_gamepad_device.h"
|
||||
|
||||
#include "base/trace_event/trace_event.h"
|
||||
|
||||
namespace device {
|
||||
|
||||
WgiGamepadDevice::WgiGamepadDevice(
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad)
|
||||
: gamepad_(std::move(gamepad)) {}
|
||||
|
||||
WgiGamepadDevice::~WgiGamepadDevice() = default;
|
||||
|
||||
void WgiGamepadDevice::SetVibration(double strong_magnitude,
|
||||
double weak_magnitude) {
|
||||
ABI::Windows::Gaming::Input::GamepadVibration vibration = {
|
||||
.LeftMotor = strong_magnitude,
|
||||
.RightMotor = weak_magnitude,
|
||||
};
|
||||
HRESULT hr = gamepad_->put_Vibration(vibration);
|
||||
DCHECK(SUCCEEDED(hr));
|
||||
}
|
||||
|
||||
base::WeakPtr<AbstractHapticGamepad> WgiGamepadDevice::GetWeakPtr() {
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
} // namespace device
|
43
device/gamepad/wgi_gamepad_device.h
Normal file
43
device/gamepad/wgi_gamepad_device.h
Normal file
@ -0,0 +1,43 @@
|
||||
// Copyright 2022 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_WGI_GAMEPAD_DEVICE_H_
|
||||
#define DEVICE_GAMEPAD_WGI_GAMEPAD_DEVICE_H_
|
||||
|
||||
#include <Windows.Gaming.Input.h>
|
||||
#include <wrl/client.h>
|
||||
#include <wrl/event.h>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/win/core_winrt_util.h"
|
||||
#include "device/gamepad/abstract_haptic_gamepad.h"
|
||||
|
||||
namespace device {
|
||||
|
||||
class DEVICE_GAMEPAD_EXPORT WgiGamepadDevice final
|
||||
: public AbstractHapticGamepad {
|
||||
public:
|
||||
explicit WgiGamepadDevice(
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad);
|
||||
WgiGamepadDevice(const WgiGamepadDevice& other) = delete;
|
||||
WgiGamepadDevice& operator=(const WgiGamepadDevice& other) = delete;
|
||||
~WgiGamepadDevice() override;
|
||||
|
||||
// AbstractHapticGamepad implementation.
|
||||
void SetVibration(double strong_magnitude, double weak_magnitude) override;
|
||||
base::WeakPtr<AbstractHapticGamepad> GetWeakPtr() override;
|
||||
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> GetGamepad() {
|
||||
return gamepad_;
|
||||
}
|
||||
|
||||
private:
|
||||
Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad_;
|
||||
|
||||
base::WeakPtrFactory<WgiGamepadDevice> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace device
|
||||
|
||||
#endif // DEVICE_GAMEPAD_WGI_GAMEPAD_DEVICE_H_
|
Reference in New Issue
Block a user