chromium/device/gamepad/wgi_data_fetcher_win.cc

// Copyright 2020 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifdef UNSAFE_BUFFERS_BUILD
// TODO(crbug.com/351564777): Remove this and convert code to safer constructs.
#pragma allow_unsafe_buffers
#endif

#include "device/gamepad/wgi_data_fetcher_win.h"

#include <XInput.h>
#include <stddef.h>
#include <stdint.h>
#include <wrl/event.h>

#include <algorithm>
#include <memory>
#include <optional>
#include <string>
#include <utility>

#include "base/containers/flat_map.h"
#include "base/sequence_checker.h"
#include "base/strings/string_util_win.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/sequenced_task_runner.h"
#include "base/win/core_winrt_util.h"
#include "base/win/hstring_reference.h"
#include "base/win/windows_version.h"
#include "device/base/event_utils_winrt.h"
#include "device/gamepad/dualshock4_controller.h"
#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 {

constexpr char16_t kKnownXInputDeviceId[] =
    u"Xbox 360 Controller (XInput STANDARD GAMEPAD)";

Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
GetRawGameController(ABI::Windows::Gaming::Input::IGamepad* gamepad,
                     WgiDataFetcherWin::GetActivationFactoryFunction
                         get_activation_factory_function_) {
  base::win::ScopedHString raw_game_controller_string =
      base::win::ScopedHString::Create(
          RuntimeClass_Windows_Gaming_Input_RawGameController);
  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameControllerStatics>
      raw_game_controller_statics;
  HRESULT hr = get_activation_factory_function_(
      raw_game_controller_string.get(),
      IID_PPV_ARGS(&raw_game_controller_statics));
  if (FAILED(hr))
    return nullptr;

  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGameController>
      game_controller;
  hr = gamepad->QueryInterface(IID_PPV_ARGS(&game_controller));
  if (FAILED(hr))
    return nullptr;

  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
      raw_game_controller;
  hr = raw_game_controller_statics->FromGameController(game_controller.Get(),
                                                       &raw_game_controller);
  if (FAILED(hr))
    return nullptr;

  return raw_game_controller;
}

std::optional<GamepadId> GetGamepadId(
    const std::u16string& product_name,
    ABI::Windows::Gaming::Input::IGamepad* gamepad,
    WgiDataFetcherWin::GetActivationFactoryFunction
        get_activation_factory_function) {
  std::string product_name_string = base::UTF16ToUTF8(product_name);
  HRESULT hr = S_OK;
  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
      raw_game_controller =
          GetRawGameController(gamepad, get_activation_factory_function);
  if (!raw_game_controller) {
    return std::nullopt;
  }

  uint16_t vendor_id;
  hr = raw_game_controller->get_HardwareVendorId(&vendor_id);
  if (FAILED(hr)) {
    return std::nullopt;
  }

  uint16_t product_id;
  hr = raw_game_controller->get_HardwareProductId(&product_id);
  if (FAILED(hr)) {
    return std::nullopt;
  }

  return GamepadIdList::Get().GetGamepadId(product_name_string, vendor_id,
                                           product_id);
}

// Check if the gamepad should be added by Windows.Gaming.Input. In the
// situation that a Nintendo or Dualshock4 gamepad is connected, there are
// dedicated data fetchers designed for these gamepads.
// We want to let those data fetchers handle the gamepad input instead.
bool ShouldEnumerateGamepad(GamepadId gamepad_id) {
  if (NintendoController::IsNintendoController(gamepad_id)) {
    // Nintendo devices are handled by the Nintendo data fetcher.
    return false;
  }

  if (Dualshock4Controller::IsDualshock4(gamepad_id)) {
    // Dualshock4 devices are handled by the RawInput data fetcher.
    return false;
  }

  return true;
}

// Checks if the provided gamepad has paddles and returns the available
// quantity. If the Windows version is less then WIN10_RS1 (WIN10.0.14393.0),
// this function returns 0, since IGamepad2 interface will not be available.
uint32_t GetPaddleNumber(
    const Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad>&
        gamepad) {
  uint32_t num_paddles = 0;
  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
    return num_paddles;
  }

  static constexpr ABI::Windows::Gaming::Input::GamepadButtons kPaddles[] = {
      ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle1,
      ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle2,
      ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle3,
      ABI::Windows::Gaming::Input::GamepadButtons::GamepadButtons_Paddle4};
  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad2> gamepad2;
  HRESULT hr = gamepad->QueryInterface(IID_PPV_ARGS(&gamepad2));
  if (hr == S_OK) {
    ABI::Windows::Gaming::Input::GameControllerButtonLabel button_label;
    for (const auto& paddle : kPaddles) {
      hr = gamepad2->GetButtonLabel(paddle, &button_label);
      if (hr == S_OK &&
          button_label !=
              ABI::Windows::Gaming::Input::GameControllerButtonLabel::
                  GameControllerButtonLabel_None) {
        ++num_paddles;
      }
    }
  }
  return num_paddles;
}

}  // namespace

WgiDataFetcherWin::WgiDataFetcherWin() {
  DETACH_FROM_SEQUENCE(sequence_checker_);
  // If a callback has been overridden previously, we'll give preference to it.
  if (GetActivationFactoryFunctionCallback()) {
    get_activation_factory_function_ =
        GetActivationFactoryFunctionCallback().Run();
  } else {
    get_activation_factory_function_ = &base::win::RoGetActivationFactory;
  }

  xinput_data_fetcher_ = std::make_unique<XInputDataFetcherWin>();
}

WgiDataFetcherWin::~WgiDataFetcherWin() {
  UnregisterEventHandlers();
  for (auto& map_entry : devices_) {
    if (map_entry.second) {
      map_entry.second->Shutdown();
    }
  }
}

GamepadSource WgiDataFetcherWin::source() {
  return Factory::static_source();
}

void WgiDataFetcherWin::OnAddedToProvider() {
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  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;
  }
  xinput_data_fetcher_->InitializeForWgiDataFetcher();
  initialization_state_ = InitializationState::kInitialized;
}

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
  // `devices_` object, making it thread-safe.
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);

  if (initialization_state_ != InitializationState::kInitialized)
    return;

  const std::u16string display_name = GetGamepadDisplayName(gamepad);
  std::optional<GamepadId> gamepad_id_optional =
      GetGamepadId(display_name, gamepad, get_activation_factory_function_);

  // If `gamepad_id_optional` has std::nullopt, it means that an error has
  // happened when calling the Windows API's.
  if (!gamepad_id_optional.has_value()) {
    return;
  }

  GamepadId gamepad_id = gamepad_id_optional.value();
  if (!ShouldEnumerateGamepad(gamepad_id)) {
    return;
  }

  int source_id = next_source_id_++;
  PadState* state = GetPadState(source_id);
  if (!state)
    return;
  state->is_initialized = true;
  Gamepad& pad = state->data;
  pad.SetID(BuildGamepadIdString(gamepad_id, display_name, gamepad));
  pad.connected = true;

  if (GamepadIdList::Get().HasTriggerRumbleSupport(gamepad_id)) {
    pad.vibration_actuator.type = GamepadHapticActuatorType::kTriggerRumble;
  } else {
    pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble;
  }

  pad.vibration_actuator.not_null = true;
  pad.mapping = GamepadMapping::kStandard;
  devices_[source_id] = std::make_unique<WgiGamepadDevice>(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
  // `devices_` object, making it thread-safe.
  DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
  DCHECK_EQ(initialization_state_, InitializationState::kInitialized);

  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_);
  // Store a pointer to the WGI PadState with the lowest index, so that we can
  // redirect any detected meta button presses to it.
  PadState* lowest_index_wgi_pad_state = nullptr;
  for (const auto& map_entry : devices_) {
    PadState* state = GetPadState(map_entry.first);
    if (!state)
      continue;

    // Check the PadState index and store it.
    if (!lowest_index_wgi_pad_state ||
        state->pad_index < lowest_index_wgi_pad_state->pad_index)
      lowest_index_wgi_pad_state = state;

    ABI::Windows::Gaming::Input::GamepadReading reading;
    Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IGamepad> gamepad =
        map_entry.second->GetGamepad();
    if (FAILED(gamepad->GetCurrentReading(&reading)))
      continue;

    Gamepad& pad = state->data;
    pad.timestamp = CurrentTimeInMicroseconds();
    pad.buttons_length = BUTTON_INDEX_COUNT + GetPaddleNumber(gamepad);

    static constexpr struct {
      int button_index;
      ABI::Windows::Gaming::Input::GamepadButtons wgi_button_mask;
    } kButtonMappings[] = {
        {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},
    };

    for (const auto& button : kButtonMappings) {
      if (reading.Buttons & button.wgi_button_mask) {
        pad.buttons[button.button_index].pressed = true;
        pad.buttons[button.button_index].value = 1.0f;
      } else {
        pad.buttons[button.button_index].pressed = false;
        pad.buttons[button.button_index].value = 0.0f;
      }
    }

    pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].pressed =
        reading.LeftTrigger > GamepadButton::kDefaultButtonPressedThreshold;
    pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].value = reading.LeftTrigger;

    pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].pressed =
        reading.RightTrigger > GamepadButton::kDefaultButtonPressedThreshold;
    pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].value = reading.RightTrigger;

    pad.axes_length = AXIS_INDEX_COUNT;

    // Invert the Y thumbstick axes to match the Standard Gamepad. WGI
    // thumbstick axes use +up/+right but the Standard Gamepad uses
    // +down/+right.
    pad.axes[AXIS_INDEX_LEFT_STICK_X] = reading.LeftThumbstickX;
    pad.axes[AXIS_INDEX_LEFT_STICK_Y] = reading.LeftThumbstickY * -1.0f;
    pad.axes[AXIS_INDEX_RIGHT_STICK_X] = reading.RightThumbstickX;
    pad.axes[AXIS_INDEX_RIGHT_STICK_Y] = reading.RightThumbstickY * -1.0f;
  }

  // We should only call the XInput functions if there are WGI gamepads added.
  // Only the lowest-index WGI gamepad should receive the meta input. Also, the
  // XInput meta input should be received even if there is an error while
  // getting the WGI reading.
  if (lowest_index_wgi_pad_state) {
    bool is_meta_pressed = xinput_data_fetcher_->IsAnyMetaButtonPressed();
    lowest_index_wgi_pad_state->data.buttons[BUTTON_INDEX_META].pressed =
        is_meta_pressed;
    lowest_index_wgi_pad_state->data.buttons[BUTTON_INDEX_META].value =
        is_meta_pressed ? 1.f : 0.f;
  }
}

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) {
  GetActivationFactoryFunctionCallback() = callback;
}

// static
WgiDataFetcherWin::ActivationFactoryFunctionCallback&
WgiDataFetcherWin::GetActivationFactoryFunctionCallback() {
  static base::NoDestructor<
      WgiDataFetcherWin::ActivationFactoryFunctionCallback>
      instance;
  return *instance;
}

WgiDataFetcherWin::InitializationState
WgiDataFetcherWin::GetInitializationState() const {
  return initialization_state_;
}

std::u16string WgiDataFetcherWin::GetGamepadDisplayName(
    ABI::Windows::Gaming::Input::IGamepad* gamepad) {
  static constexpr char16_t kDefaultDisplayName[] = u"Unknown Gamepad";

  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController>
      raw_game_controller = ::device::GetRawGameController(
          gamepad, get_activation_factory_function_);

  if (!raw_game_controller) {
    return kDefaultDisplayName;
  }

  Microsoft::WRL::ComPtr<ABI::Windows::Gaming::Input::IRawGameController2>
      raw_game_controller2;
  if (FAILED(raw_game_controller.As(&raw_game_controller2))) {
    return kDefaultDisplayName;
  }

  HSTRING display_name;
  if (FAILED(raw_game_controller2->get_DisplayName(&display_name))) {
    return kDefaultDisplayName;
  }
  base::win::ScopedHString scoped_display_name(display_name);
  return base::AsString16(scoped_display_name.Get());
}

std::u16string WgiDataFetcherWin::BuildGamepadIdString(
    GamepadId gamepad_id,
    const std::u16string& display_name,
    ABI::Windows::Gaming::Input::IGamepad* gamepad) {
  // Return early for GamepadId::kUnknownGamepad because
  // GetDeviceIdsFromGamepadId has a DCHECK against it.
  if (gamepad_id == GamepadId::kUnknownGamepad) {
    return display_name + u" (STANDARD GAMEPAD)";
  }

  uint16_t vendor_id, product_id;
  std::tie(vendor_id, product_id) =
      GamepadIdList::Get().GetDeviceIdsFromGamepadId(gamepad_id);
  XInputType xinput_type =
      GamepadIdList::Get().GetXInputType(vendor_id, product_id);
  if (xinput_type == kXInputTypeNone) {
    return display_name + base::ASCIIToUTF16(base::StringPrintf(
                              " (STANDARD GAMEPAD Vendor: %04x Product: %04x)",
                              vendor_id, product_id));
  }

  // If the device is an already known XInput device that is now being
  // enumerated by WGI, return the old XInput id string.
  return kKnownXInputDeviceId;
}

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);
    }
  }
}

}  // namespace device