blob: 792db569e3438d10ed6e90f3c13f5638ee810409 [file] [log] [blame]
// Copyright 2020 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 <wrl/event.h>
#include <string>
#include "base/containers/cxx20_erase.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util_win.h"
#include "base/threading/sequenced_task_runner_handle.h"
#include "base/win/core_winrt_util.h"
#include "base/win/hstring_reference.h"
#include "device/base/event_utils_winrt.h"
namespace device {
WgiDataFetcherWin::WgiDataFetcherWin() {
DETACH_FROM_SEQUENCE(sequence_checker_);
get_activation_factory_function_ = &base::win::RoGetActivationFactory;
}
WgiDataFetcherWin::~WgiDataFetcherWin() {
UnregisterEventHandlers();
}
GamepadSource WgiDataFetcherWin::source() {
return Factory::static_source();
}
void WgiDataFetcherWin::OnAddedToProvider() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!base::win::HStringReference::ResolveCoreWinRTStringDelayload()) {
initialization_state_ =
InitializationState::kCoreWinrtStringDelayLoadFailed;
return;
}
HRESULT hr = get_activation_factory_function_(
base::win::HStringReference(RuntimeClass_Windows_Gaming_Input_Gamepad)
.Get(),
IID_PPV_ARGS(&gamepad_statics_));
if (FAILED(hr)) {
initialization_state_ = InitializationState::kRoGetActivationFactoryFailed;
return;
}
// Create a Windows::Foundation::IEventHandler that runs a
// base::RepeatingCallback() on the gamepad polling thread when a gamepad
// is added or removed. This callback stores the current sequence task runner
// and weak pointer, so those two objects would remain active until the
// callback returns.
added_event_token_ = AddEventHandler(
gamepad_statics_.Get(),
&ABI::Windows::Gaming::Input::IGamepadStatics::add_GamepadAdded,
base::BindRepeating(&WgiDataFetcherWin::OnGamepadAdded,
weak_factory_.GetWeakPtr()));
if (!added_event_token_) {
initialization_state_ = InitializationState::kAddGamepadAddedFailed;
UnregisterEventHandlers();
return;
}
removed_event_token_ = AddEventHandler(
gamepad_statics_.Get(),
&ABI::Windows::Gaming::Input::IGamepadStatics::add_GamepadRemoved,
base::BindRepeating(&WgiDataFetcherWin::OnGamepadRemoved,
weak_factory_.GetWeakPtr()));
if (!removed_event_token_) {
initialization_state_ = InitializationState::kAddGamepadRemovedFailed;
UnregisterEventHandlers();
return;
}
initialization_state_ = InitializationState::kInitialized;
}
void WgiDataFetcherWin::SetGetActivationFunctionForTesting(
GetActivationFactoryFunction value) {
get_activation_factory_function_ = value;
}
void WgiDataFetcherWin::OnGamepadAdded(
IInspectable* /* sender */,
ABI::Windows::Gaming::Input::IGamepad* gamepad) {
// While base::win::AddEventHandler stores the sequence_task_runner in the
// callback function object, it post the task back to the same sequence - the
// 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.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (initialization_state_ != InitializationState::kInitialized)
return;
int source_id = next_source_id_++;
PadState* state = GetPadState(source_id);
if (!state)
return;
state->is_initialized = true;
Gamepad& pad = state->data;
pad.connected = true;
pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble;
pad.vibration_actuator.not_null = false;
pad.mapping = GamepadMapping::kStandard;
gamepads_.push_back({source_id, gamepad});
}
void WgiDataFetcherWin::OnGamepadRemoved(
IInspectable* /* sender */,
ABI::Windows::Gaming::Input::IGamepad* gamepad) {
// While ::device::AddEventHandler stores the sequence_task_runner in the
// callback function object, it post the task back to the same sequence - the
// 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.
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK_EQ(initialization_state_, InitializationState::kInitialized);
base::EraseIf(gamepads_,
[=](const WindowsGamingInputControllerMapping& mapping) {
return mapping.gamepad.Get() == gamepad;
});
}
void WgiDataFetcherWin::GetGamepadData(bool devices_changed_hint) {
// TODO(https://crbug.com/1105671): Implement the WgiDataFetcherWin
// and add corresponding tests
}
const std::vector<WgiDataFetcherWin::WindowsGamingInputControllerMapping>&
WgiDataFetcherWin::GetGamepadsForTesting() const {
return gamepads_;
}
WgiDataFetcherWin::InitializationState
WgiDataFetcherWin::GetInitializationState() const {
return initialization_state_;
}
void WgiDataFetcherWin::UnregisterEventHandlers() {
if (added_event_token_) {
HRESULT hr =
gamepad_statics_->remove_GamepadAdded(added_event_token_.value());
if (FAILED(hr)) {
DLOG(ERROR) << "Removing GamepadAdded Handler failed: "
<< logging::SystemErrorCodeToString(hr);
}
}
if (removed_event_token_) {
HRESULT hr =
gamepad_statics_->remove_GamepadRemoved(removed_event_token_.value());
if (FAILED(hr)) {
DLOG(ERROR) << "Removing GamepadRemoved Handler failed: "
<< logging::SystemErrorCodeToString(hr);
}
}
}
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