| // Copyright 2021 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_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" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/thread_pool/thread_pool_instance.h" |
| #include "base/threading/thread.h" |
| #include "base/time/time.h" |
| #include "base/win/scoped_hstring.h" |
| #include "base/win/windows_version.h" |
| #include "device/gamepad/gamepad_id_list.h" |
| #include "device/gamepad/gamepad_pad_state_provider.h" |
| #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" |
| |
| namespace device { |
| |
| using ::ABI::Windows::Gaming::Input::GamepadReading; |
| |
| constexpr uint16_t kHardwareProductId = 1; |
| constexpr uint16_t kHardwareVendorId = 100; |
| |
| constexpr char kGamepadDisplayName[] = "XBOX_SERIES_X"; |
| constexpr double kErrorTolerance = 1e-5; |
| |
| constexpr unsigned int kGamepadButtonsLength = 16; |
| // The Meta button position at CanonicalButtonIndex::BUTTON_INDEX_META in the |
| // buttons array should be occupied with a NullButton. So we end up with: |
| // 16 buttons + 1 null button + 4 paddles = 21 buttons. |
| 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, |
| ErrorCode::kErrorWgiRawGameControllerGetHardwareProductIdFailed, |
| ErrorCode::kErrorWgiRawGameControllerGetHardwareVendorIdFailed}; |
| |
| // GamepadReading struct for a gamepad in resting position. |
| constexpr ABI::Windows::Gaming::Input::GamepadReading |
| kZeroPositionGamepadReading = { |
| .Timestamp = 1234, |
| .Buttons = |
| ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_None, |
| .LeftTrigger = 0, |
| .RightTrigger = 0, |
| .LeftThumbstickX = 0, |
| .LeftThumbstickY = 0.05, |
| .RightThumbstickX = -0.08, |
| .RightThumbstickY = 0}; |
| |
| // Sample GamepadReading struct for testing. |
| constexpr ABI::Windows::Gaming::Input::GamepadReading kGamepadReading = { |
| .Timestamp = 1234, |
| .Buttons = |
| ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_A | |
| ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_DPadDown | |
| ABI::Windows::Gaming::Input::GamepadButtons:: |
| GamepadButtons_RightShoulder | |
| ABI::Windows::Gaming::Input::GamepadButtons:: |
| GamepadButtons_LeftThumbstick | |
| ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle1 | |
| ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle3, |
| .LeftTrigger = 1.0, |
| .RightTrigger = 0.6, |
| .LeftThumbstickX = 0.2, |
| .LeftThumbstickY = 1.0, |
| .RightThumbstickX = -0.4, |
| .RightThumbstickY = 0.6}; |
| |
| class WgiDataFetcherWinTest : public DeviceServiceTestBase { |
| public: |
| WgiDataFetcherWinTest() = default; |
| ~WgiDataFetcherWinTest() override = default; |
| |
| void SetUp() override { |
| // Windows.Gaming.Input is available in Windows 10.0.10240.0 and later. |
| if (base::win::GetVersion() < base::win::Version::WIN10) |
| GTEST_SKIP(); |
| DeviceServiceTestBase::SetUp(); |
| } |
| |
| void SetUpTestEnv(ErrorCode error_code = ErrorCode::kOk) { |
| EXPECT_TRUE(base::win::ScopedHString::ResolveCoreWinRTStringDelayload()); |
| wgi_environment_ = std::make_unique<FakeWinrtWgiEnvironment>(error_code); |
| auto fetcher = std::make_unique<WgiDataFetcherWin>(); |
| fetcher_ = fetcher.get(); |
| |
| // Initialize provider to retrieve pad state. |
| auto polling_thread = std::make_unique<base::Thread>("polling thread"); |
| polling_thread_ = polling_thread.get(); |
| provider_ = std::make_unique<GamepadProvider>( |
| /*connection_change_client=*/nullptr, std::move(fetcher), |
| std::move(polling_thread)); |
| |
| FlushPollingThread(); |
| } |
| |
| void FlushPollingThread() { polling_thread_->FlushForTesting(); } |
| |
| // Sleep until the shared memory buffer's seqlock advances the buffer version, |
| // indicating that the gamepad provider has written to it after polling the |
| // gamepad fetchers. The buffer will report an odd value for the version if |
| // the buffer is not in a consistent state, so we also require that the value |
| // is even before continuing. |
| void WaitForData(const GamepadHardwareBuffer* buffer) { |
| const base::subtle::Atomic32 initial_version = buffer->seqlock.ReadBegin(); |
| base::subtle::Atomic32 current_version; |
| do { |
| base::PlatformThread::Sleep(base::Milliseconds(10)); |
| current_version = buffer->seqlock.ReadBegin(); |
| } while (current_version % 2 || current_version == initial_version); |
| } |
| |
| void ReadGamepadHardwareBuffer(const GamepadHardwareBuffer* buffer, |
| Gamepads* output) { |
| memset(output, 0, sizeof(Gamepads)); |
| base::subtle::Atomic32 version; |
| do { |
| version = buffer->seqlock.ReadBegin(); |
| memcpy(output, &buffer->data, sizeof(Gamepads)); |
| } while (buffer->seqlock.ReadRetry(version)); |
| } |
| |
| void CheckGamepadAdded(PadState* pad_state) { |
| // Check size of connected gamepad list and ensure gamepad state |
| // is initialized correctly. |
| EXPECT_TRUE(pad_state->is_initialized); |
| Gamepad& pad = pad_state->data; |
| EXPECT_TRUE(pad.connected); |
| EXPECT_TRUE(pad.vibration_actuator.not_null); |
| } |
| |
| void CheckGamepadRemoved() { |
| EXPECT_TRUE(fetcher().GetGamepadsForTesting().empty()); |
| } |
| |
| void CheckButtonState( |
| int canonical_button_index, |
| ABI::Windows::Gaming::Input::GamepadButtons input_buttons_bit_mask, |
| const GamepadButton output_buttons[]) { |
| static constexpr auto kCanonicalButtonBitMaskMapping = |
| base::MakeFixedFlatMap<int, |
| ABI::Windows::Gaming::Input::GamepadButtons>( |
| {{BUTTON_INDEX_PRIMARY, |
| ABI::Windows::Gaming::Input::GamepadButtons_A}, |
| {BUTTON_INDEX_SECONDARY, |
| ABI::Windows::Gaming::Input::GamepadButtons_B}, |
| {BUTTON_INDEX_TERTIARY, |
| ABI::Windows::Gaming::Input::GamepadButtons_X}, |
| {BUTTON_INDEX_QUATERNARY, |
| ABI::Windows::Gaming::Input::GamepadButtons_Y}, |
| {BUTTON_INDEX_LEFT_SHOULDER, |
| ABI::Windows::Gaming::Input::GamepadButtons_LeftShoulder}, |
| {BUTTON_INDEX_RIGHT_SHOULDER, |
| ABI::Windows::Gaming::Input::GamepadButtons_RightShoulder}, |
| {BUTTON_INDEX_BACK_SELECT, |
| ABI::Windows::Gaming::Input::GamepadButtons_View}, |
| {BUTTON_INDEX_START, |
| ABI::Windows::Gaming::Input::GamepadButtons_Menu}, |
| {BUTTON_INDEX_LEFT_THUMBSTICK, |
| ABI::Windows::Gaming::Input::GamepadButtons_LeftThumbstick}, |
| {BUTTON_INDEX_RIGHT_THUMBSTICK, |
| ABI::Windows::Gaming::Input::GamepadButtons_RightThumbstick}, |
| {BUTTON_INDEX_DPAD_UP, |
| ABI::Windows::Gaming::Input::GamepadButtons_DPadUp}, |
| {BUTTON_INDEX_DPAD_DOWN, |
| ABI::Windows::Gaming::Input::GamepadButtons_DPadDown}, |
| {BUTTON_INDEX_DPAD_LEFT, |
| ABI::Windows::Gaming::Input::GamepadButtons_DPadLeft}, |
| {BUTTON_INDEX_DPAD_RIGHT, |
| ABI::Windows::Gaming::Input::GamepadButtons_DPadRight}, |
| {BUTTON_INDEX_META + 1, |
| ABI::Windows::Gaming::Input::GamepadButtons_Paddle1}, |
| {BUTTON_INDEX_META + 2, |
| ABI::Windows::Gaming::Input::GamepadButtons_Paddle2}, |
| {BUTTON_INDEX_META + 3, |
| ABI::Windows::Gaming::Input::GamepadButtons_Paddle3}, |
| {BUTTON_INDEX_META + 4, |
| ABI::Windows::Gaming::Input::GamepadButtons_Paddle4}}); |
| |
| // WGI does not have support to the Meta button. |
| if (canonical_button_index == BUTTON_INDEX_META) { |
| EXPECT_FALSE(output_buttons[canonical_button_index].pressed); |
| EXPECT_FALSE(output_buttons[canonical_button_index].touched); |
| EXPECT_FALSE(output_buttons[canonical_button_index].used); |
| EXPECT_NEAR(output_buttons[canonical_button_index].value, 0.0f, |
| kErrorTolerance); |
| return; |
| } |
| |
| const auto* button_bit_mask = |
| kCanonicalButtonBitMaskMapping.find(canonical_button_index); |
| if (button_bit_mask == kCanonicalButtonBitMaskMapping.end()) { |
| ADD_FAILURE() << "Unsupported CanonicalButtonIndex value: " |
| << canonical_button_index; |
| return; |
| } |
| |
| if (input_buttons_bit_mask & button_bit_mask->second) { |
| EXPECT_TRUE(output_buttons[canonical_button_index].pressed); |
| EXPECT_NEAR(output_buttons[canonical_button_index].value, 1.0f, |
| kErrorTolerance); |
| } else { |
| EXPECT_FALSE(output_buttons[canonical_button_index].pressed); |
| EXPECT_NEAR(output_buttons[canonical_button_index].value, 0.0f, |
| kErrorTolerance); |
| } |
| } |
| |
| void CheckGamepadInputResult(const GamepadReading& input, |
| const Gamepad& output, |
| bool has_paddles = false) { |
| if (has_paddles) { |
| EXPECT_EQ(output.buttons_length, kGamepadWithPaddlesButtonsLength); |
| } else { |
| EXPECT_EQ(output.buttons_length, kGamepadButtonsLength); |
| } |
| |
| CheckButtonState(BUTTON_INDEX_PRIMARY, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_SECONDARY, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_TERTIARY, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_QUATERNARY, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_LEFT_SHOULDER, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_RIGHT_SHOULDER, input.Buttons, |
| output.buttons); |
| CheckButtonState(BUTTON_INDEX_BACK_SELECT, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_START, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_LEFT_THUMBSTICK, input.Buttons, |
| output.buttons); |
| CheckButtonState(BUTTON_INDEX_RIGHT_THUMBSTICK, input.Buttons, |
| output.buttons); |
| CheckButtonState(BUTTON_INDEX_DPAD_UP, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_DPAD_DOWN, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_DPAD_LEFT, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_DPAD_RIGHT, input.Buttons, output.buttons); |
| if (has_paddles) { |
| // The Meta button position at CanonicalButtonIndex::BUTTON_INDEX_META in |
| // the buttons array should be occupied with a NullButton when paddles are |
| // present. |
| CheckButtonState(BUTTON_INDEX_META, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_META + 1, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_META + 2, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_META + 3, input.Buttons, output.buttons); |
| CheckButtonState(BUTTON_INDEX_META + 4, input.Buttons, output.buttons); |
| } |
| |
| EXPECT_EQ(input.LeftTrigger > GamepadButton::kDefaultButtonPressedThreshold, |
| output.buttons[BUTTON_INDEX_LEFT_TRIGGER].pressed); |
| EXPECT_NEAR(input.LeftTrigger, |
| output.buttons[BUTTON_INDEX_LEFT_TRIGGER].value, |
| kErrorTolerance); |
| |
| EXPECT_EQ( |
| input.RightTrigger > GamepadButton::kDefaultButtonPressedThreshold, |
| output.buttons[BUTTON_INDEX_RIGHT_TRIGGER].pressed); |
| EXPECT_NEAR(input.RightTrigger, |
| output.buttons[BUTTON_INDEX_RIGHT_TRIGGER].value, |
| kErrorTolerance); |
| |
| // Invert the Y thumbstick axes to match the Standard Gamepad. WGI |
| // thumbstick axes use +up/+right but the Standard Gamepad uses |
| // +down/+right. |
| EXPECT_EQ(output.axes_length, kGamepadAxesLength); |
| EXPECT_NEAR(input.LeftThumbstickX, output.axes[AXIS_INDEX_LEFT_STICK_X], |
| kErrorTolerance); |
| EXPECT_NEAR(input.LeftThumbstickY, |
| -1.0f * output.axes[AXIS_INDEX_LEFT_STICK_Y], kErrorTolerance); |
| EXPECT_NEAR(input.RightThumbstickX, output.axes[AXIS_INDEX_RIGHT_STICK_X], |
| kErrorTolerance); |
| EXPECT_NEAR(input.RightThumbstickY, |
| -1.0f * output.axes[AXIS_INDEX_RIGHT_STICK_Y], kErrorTolerance); |
| } |
| |
| 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_; |
| |
| private: |
| raw_ptr<WgiDataFetcherWin> fetcher_; |
| raw_ptr<base::Thread> polling_thread_; |
| }; |
| |
| TEST_F(WgiDataFetcherWinTest, AddAndRemoveWgiGamepad) { |
| SetUpTestEnv(); |
| |
| // Check initial number of connected gamepad and WGI initialization status. |
| EXPECT_EQ(fetcher().GetInitializationState(), |
| WgiDataFetcherWin::InitializationState::kInitialized); |
| EXPECT_TRUE(fetcher().GetGamepadsForTesting().empty()); |
| |
| const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>(); |
| auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance(); |
| |
| // Check that the event handlers were added. |
| EXPECT_EQ(fake_gamepad_statics->GetGamepadAddedEventHandlerCount(), 1u); |
| EXPECT_EQ(fake_gamepad_statics->GetGamepadRemovedEventHandlerCount(), 1u); |
| |
| // Simulate the gamepad adding behavior by passing an IGamepad, and make |
| // the gamepad-adding callback return on a different thread, demonstrated the |
| // multi-threaded apartments setting of the GamepadStatics COM API. |
| // Corresponding threading simulation is in FakeIGamepadStatics class. |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad, kHardwareProductId, kHardwareVendorId, kGamepadDisplayName); |
| |
| // Wait for the gampad polling thread to handle the gamepad added event. |
| FlushPollingThread(); |
| |
| // Assert that the gamepad has been added to the DataFetcher. |
| const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads = |
| fetcher().GetGamepadsForTesting(); |
| ASSERT_EQ(gamepads.size(), 1u); |
| 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 |
| // apartments setting of the GamepadStatics COM API. Corresponding threading |
| // simulation is in FakeIGamepadStatics class. |
| fake_gamepad_statics->SimulateGamepadRemoved(fake_gamepad); |
| |
| // Wait for the gampad polling thread to handle the gamepad removed event. |
| FlushPollingThread(); |
| |
| CheckGamepadRemoved(); |
| } |
| |
| TEST_F(WgiDataFetcherWinTest, AddGamepadAddedEventHandlerErrorHandling) { |
| // Let fake gamepad statics add_GamepadAdded return failure code to |
| // test error handling. |
| SetUpTestEnv(ErrorCode::kGamepadAddGamepadAddedFailed); |
| |
| // Check WGI initialization status. |
| EXPECT_EQ(fetcher().GetInitializationState(), |
| WgiDataFetcherWin::InitializationState::kAddGamepadAddedFailed); |
| auto* gamepad_statics = FakeIGamepadStatics::GetInstance(); |
| EXPECT_EQ(gamepad_statics->GetGamepadAddedEventHandlerCount(), 0u); |
| } |
| |
| TEST_F(WgiDataFetcherWinTest, AddGamepadRemovedEventHandlerErrorHandling) { |
| // Let fake gamepad statics add_GamepadRemoved return failure code to |
| // test error handling. |
| SetUpTestEnv(ErrorCode::kGamepadAddGamepadRemovedFailed); |
| |
| // Check WGI initialization status. |
| EXPECT_EQ(fetcher().GetInitializationState(), |
| WgiDataFetcherWin::InitializationState::kAddGamepadRemovedFailed); |
| auto* gamepad_statics = FakeIGamepadStatics::GetInstance(); |
| EXPECT_EQ(gamepad_statics->GetGamepadRemovedEventHandlerCount(), 0u); |
| } |
| |
| TEST_F(WgiDataFetcherWinTest, WgiGamepadActivationFactoryErrorHandling) { |
| // Let fake RoGetActivationFactory return failure code to |
| // test error handling. |
| SetUpTestEnv(ErrorCode::kErrorWgiGamepadActivateFailed); |
| |
| // Check WGI initialization status. |
| EXPECT_EQ( |
| fetcher().GetInitializationState(), |
| WgiDataFetcherWin::InitializationState::kRoGetActivationFactoryFailed); |
| } |
| |
| // If RawGameController2::get_DisplayName fails when calling |
| // WgiDataFetcherWin::GetDisplayName, a gamepad will be added with a default |
| // DisplayName. |
| TEST_F(WgiDataFetcherWinTest, FailuretoGetDisplayNameOnGamepadAdded) { |
| constexpr char16_t kDefaultDisplayName[] = u"Unknown Gamepad"; |
| SetUpTestEnv(ErrorCode::kErrorWgiRawGameControllerGetDisplayNameFailed); |
| |
| const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>(); |
| auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance(); |
| |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad, kHardwareProductId, kHardwareVendorId, kGamepadDisplayName); |
| |
| // Wait for the gampad polling thread to handle the gamepad added event. |
| FlushPollingThread(); |
| |
| // Assert that the gamepad has not been added to the DataFetcher. |
| const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads = |
| fetcher().GetGamepadsForTesting(); |
| ASSERT_EQ(gamepads.size(), 1u); |
| PadState* pad = fetcher().GetPadState(gamepads.begin()->first); |
| std::u16string display_id(pad->data.id); |
| EXPECT_EQ(kDefaultDisplayName, display_id); |
| CheckGamepadAdded(pad); |
| } |
| |
| TEST_F(WgiDataFetcherWinTest, VerifyGamepadInput) { |
| 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>(); |
| const auto fake_gamepad_with_paddles = Microsoft::WRL::Make<FakeIGamepad>(); |
| fake_gamepad_with_paddles->SetHasPaddles(true); |
| |
| // Add a simulated WGI device. |
| provider_->Resume(); |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad, kHardwareProductId, kHardwareVendorId, kGamepadDisplayName); |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad_with_paddles, kHardwareProductId, kHardwareVendorId, |
| kGamepadDisplayName); |
| |
| base::ReadOnlySharedMemoryRegion shared_memory_region = |
| provider_->DuplicateSharedMemoryRegion(); |
| base::ReadOnlySharedMemoryMapping shared_memory_mapping = |
| shared_memory_region.Map(); |
| EXPECT_TRUE(shared_memory_mapping.IsValid()); |
| const GamepadHardwareBuffer* gamepad_buffer = |
| static_cast<const GamepadHardwareBuffer*>(shared_memory_mapping.memory()); |
| |
| // State should be first set to the rest position to satisfy sanitization pre- |
| // requisites. |
| fake_gamepad->SetCurrentReading(kZeroPositionGamepadReading); |
| fake_gamepad_with_paddles->SetCurrentReading(kZeroPositionGamepadReading); |
| WaitForData(gamepad_buffer); |
| |
| fake_gamepad->SetCurrentReading(kGamepadReading); |
| fake_gamepad_with_paddles->SetCurrentReading(kGamepadReading); |
| |
| WaitForData(gamepad_buffer); |
| |
| Gamepads output; |
| ReadGamepadHardwareBuffer(gamepad_buffer, &output); |
| |
| // Get connected gamepad state to verify gamepad input results. |
| CheckGamepadInputResult(kGamepadReading, output.items[0]); |
| CheckGamepadInputResult(kGamepadReading, output.items[1], |
| /*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) { |
| 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>(); |
| |
| // State should be first set to the rest position to satisfy sanitization pre- |
| // requisites. |
| fake_gamepad->SetCurrentReading(kZeroPositionGamepadReading); |
| |
| // Add a simulated WGI device. |
| provider_->Resume(); |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad, kHardwareProductId, kHardwareVendorId, kGamepadDisplayName); |
| |
| base::ReadOnlySharedMemoryRegion shared_memory_region = |
| provider_->DuplicateSharedMemoryRegion(); |
| base::ReadOnlySharedMemoryMapping shared_memory_mapping = |
| shared_memory_region.Map(); |
| EXPECT_TRUE(shared_memory_mapping.IsValid()); |
| const GamepadHardwareBuffer* gamepad_buffer = |
| static_cast<const GamepadHardwareBuffer*>(shared_memory_mapping.memory()); |
| |
| WaitForData(gamepad_buffer); |
| |
| wgi_environment_->SimulateError( |
| ErrorCode::kErrorWgiGamepadGetCurrentReadingFailed); |
| |
| fake_gamepad->SetCurrentReading(kGamepadReading); |
| |
| WaitForData(gamepad_buffer); |
| |
| Gamepads output; |
| ReadGamepadHardwareBuffer(gamepad_buffer, &output); |
| |
| // Get connected gamepad state to verify gamepad input results. |
| CheckGamepadInputResult(kZeroPositionGamepadReading, output.items[0]); |
| } |
| |
| // If Gamepad::GetButtonLabel fails, the stored gamepad state buttons_length |
| // property will be equal to 16, even though the connected device may have |
| // paddles - i.e., the paddles will not be recognized. |
| TEST_F(WgiDataFetcherWinTest, WgiGamepadGetButtonLabelError) { |
| SetUpTestEnv(ErrorCode::kErrorWgiGamepadGetButtonLabelFailed); |
| |
| // 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_with_paddles = Microsoft::WRL::Make<FakeIGamepad>(); |
| fake_gamepad_with_paddles->SetHasPaddles(true); |
| |
| // State should be first set to the rest position to satisfy sanitization pre- |
| // requisites. |
| fake_gamepad_with_paddles->SetCurrentReading(kZeroPositionGamepadReading); |
| |
| // Add a simulated WGI device. |
| provider_->Resume(); |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad_with_paddles, kHardwareProductId, kHardwareVendorId, |
| kGamepadDisplayName); |
| |
| base::ReadOnlySharedMemoryRegion shared_memory_region = |
| provider_->DuplicateSharedMemoryRegion(); |
| base::ReadOnlySharedMemoryMapping shared_memory_mapping = |
| shared_memory_region.Map(); |
| EXPECT_TRUE(shared_memory_mapping.IsValid()); |
| const GamepadHardwareBuffer* gamepad_buffer = |
| static_cast<const GamepadHardwareBuffer*>(shared_memory_mapping.memory()); |
| |
| WaitForData(gamepad_buffer); |
| |
| fake_gamepad_with_paddles->SetCurrentReading(kGamepadReading); |
| |
| WaitForData(gamepad_buffer); |
| |
| Gamepads output; |
| ReadGamepadHardwareBuffer(gamepad_buffer, &output); |
| |
| // Get connected gamepad state to verify gamepad input results. |
| CheckGamepadInputResult(kGamepadReading, output.items[0]); |
| } |
| |
| // This test checks that the WgiDataFetcherWin did not enumerate any controllers |
| // it was not supposed to - e.g., Dualshock and Nintendo controllers. |
| TEST_F(WgiDataFetcherWinTest, ShouldNotEnumerateControllers) { |
| SetUpTestEnv(); |
| constexpr GamepadId kShouldNotEnumerateControllers[] = { |
| GamepadId::kNintendoProduct2006, GamepadId::kNintendoProduct2007, |
| GamepadId::kNintendoProduct2009, GamepadId::kNintendoProduct200e, |
| GamepadId::kSonyProduct05c4, GamepadId::kSonyProduct09cc}; |
| |
| auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance(); |
| |
| for (const GamepadId& device_id : kShouldNotEnumerateControllers) { |
| const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>(); |
| uint16_t vendor_id; |
| uint16_t product_id; |
| std::tie(vendor_id, product_id) = |
| GamepadIdList::Get().GetDeviceIdsFromGamepadId(device_id); |
| fake_gamepad_statics->SimulateGamepadAdded(fake_gamepad, product_id, |
| vendor_id, ""); |
| } |
| |
| // Wait for the gampad polling thread to handle the gamepad added event. |
| FlushPollingThread(); |
| |
| // Assert that the gamepad has not been added to the DataFetcher. |
| const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads = |
| fetcher().GetGamepadsForTesting(); |
| EXPECT_EQ(gamepads.size(), 0u); |
| } |
| |
| // class created to simulate scenarios where the OS may throw errors. |
| class WgiDataFetcherWinErrorTest |
| : public WgiDataFetcherWinTest, |
| public testing::WithParamInterface<ErrorCode> {}; |
| |
| // This test simulates OS errors that prevent the controller from being |
| // enumerated by WgiDataFetcherWin. |
| TEST_P(WgiDataFetcherWinErrorTest, GamepadShouldNotbeEnumerated) { |
| const ErrorCode error_code = GetParam(); |
| SetUpTestEnv(error_code); |
| const auto fake_gamepad = Microsoft::WRL::Make<FakeIGamepad>(); |
| auto* fake_gamepad_statics = FakeIGamepadStatics::GetInstance(); |
| |
| fake_gamepad_statics->SimulateGamepadAdded( |
| fake_gamepad, kHardwareProductId, kHardwareVendorId, kGamepadDisplayName); |
| |
| // Wait for the gampad polling thread to handle the gamepad added event. |
| FlushPollingThread(); |
| |
| // Assert that the gamepad has not been added to the DataFetcher. |
| const base::flat_map<int, std::unique_ptr<WgiGamepadDevice>>& gamepads = |
| fetcher().GetGamepadsForTesting(); |
| EXPECT_EQ(gamepads.size(), 0u); |
| } |
| INSTANTIATE_TEST_SUITE_P(WgiDataFetcherWinErrorTests, |
| WgiDataFetcherWinErrorTest, |
| testing::ValuesIn(kErrors)); |
| |
| } // namespace device |