910e62b5创建于 1月15日历史提交
// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "device/gamepad/dualshock4_controller.h"

#include <algorithm>
#include <array>

#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/metrics/crc32.h"
#include "base/numerics/safe_conversions.h"
#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/hid_writer.h"
#include "device/gamepad/public/cpp/gamepad_features.h"

namespace device {

namespace {
// Expected values for |version_number| when connected over USB and Bluetooth.
const uint16_t kDualshock4VersionUsb = 0x0100;
const uint16_t kDualshock4VersionBluetooth = 0;

// Report IDs.
constexpr uint8_t kReportId01 = 0x01;
constexpr uint8_t kReportId05 = 0x05;
constexpr uint8_t kReportId11 = 0x11;

// Maximum in-range values for HID report fields.
const uint8_t kRumbleMagnitudeMax = 0xff;
const float kAxisMax = 255.0f;
const float kDpadMax = 7.0f;

// Dualshock 4 touchpad absolute dimension.
constexpr uint16_t kTouchDimensionX = 1920;
constexpr uint16_t kTouchDimensionY = 942;

struct PACKED_OBJ ControllerData {
  uint8_t axis_left_x;
  uint8_t axis_left_y;
  uint8_t axis_right_x;
  uint8_t axis_right_y;
  uint8_t axis_dpad : 4;
  bool button_square : 1;
  bool button_cross : 1;
  bool button_circle : 1;
  bool button_triangle : 1;
  bool button_left_1 : 1;
  bool button_right_1 : 1;
  bool button_left_2 : 1;
  bool button_right_2 : 1;
  bool button_share : 1;
  bool button_options : 1;
  bool button_left_3 : 1;
  bool button_right_3 : 1;
  bool button_ps : 1;
  bool button_touch : 1;
  uint8_t sequence_number : 6;
  uint8_t axis_left_2;
  uint8_t axis_right_2;
  uint16_t timestamp;
  uint8_t sensor_temperature;
  uint16_t axis_gyro_pitch;
  uint16_t axis_gyro_yaw;
  uint16_t axis_gyro_roll;
  uint16_t axis_accelerometer_x;
  uint16_t axis_accelerometer_y;
  uint16_t axis_accelerometer_z;
  uint8_t padding1[5];
  uint8_t battery_info : 5;
  uint8_t padding2 : 2;
  bool extension_detection : 1;
};

static_assert(sizeof(ControllerData) == 30,
              "ControllerData has incorrect size");

struct TouchData {
  uint8_t id : 7;
  bool is_invalid : 1;
  uint8_t data[3];
};

static_assert(sizeof(TouchData) == 4, "TouchPadData has incorrect size");

struct TouchPadData {
  uint8_t touch_data_timestamp;
  std::array<TouchData, 2> touch;
};

static_assert(sizeof(TouchPadData) == 9, "TouchPadData has incorrect size");

struct Dualshock4InputReportUsb {
  ControllerData controller_data;
  uint8_t padding1[2];
  uint8_t touches_count;
  TouchPadData touches[3];
  uint8_t padding2[4];
};

static_assert(sizeof(Dualshock4InputReportUsb) == 64,
              "Dualshock4InputReportUsb has incorrect size");

struct PACKED_OBJ Dualshock4InputReportBluetooth {
  uint8_t padding1[2];
  ControllerData controller_data;
  uint8_t padding2[2];
  uint8_t touches_count;
  TouchPadData touches[4];
  uint8_t padding3[2];
  uint32_t crc32;
};

static_assert(sizeof(Dualshock4InputReportBluetooth) == 77,
              "Dualshock4InputReportBluetooth has incorrect size");

// Returns the CRC32 checksum for a Dualshock4 Bluetooth output report.
// |report_data| is the report data excluding the bytes where the checksum will
// be written.
uint32_t ComputeDualshock4Checksum(base::span<const uint8_t> report_data) {
  // The Bluetooth report checksum includes a constant header byte not contained
  // in the report data.
  constexpr uint8_t bt_header = 0xa2;
  uint32_t crc = base::Crc32(0xffffffff, base::span_from_ref(bt_header));
  // Extend the checksum with the contents of the report.
  return ~base::Crc32(crc, report_data);
}

// Scales |value| with range [0,255] to a float within [-1.0,+1.0].
static float NormalizeAxis(uint8_t value) {
  return (2.0f * value / kAxisMax) - 1.0f;
}

// Scales a D-pad value from a Dualshock4 report to an axis value. The D-pad
// report value has a logical range from 0 to 7, and uses an out-of-bounds value
// of 8 to indicate null input (no interaction). In-bounds values are scaled to
// the range [-1.0,+1.0]. The null input value returns +9/7 (about 1.29).
static float NormalizeDpad(uint8_t value) {
  return (2.0f * value / kDpadMax) - 1.0f;
}

// Scales the Dualshock4 touch absolute coordinates to a float in within the
// range [-1.0,+1.0].
float NormalizeTouch(uint32_t value, uint32_t min, uint32_t max) {
  DCHECK_LT(min, max);

  uint32_t clamped_value = std::min(max, std::max(min, value));
  return (2.0f * (clamped_value - min) / static_cast<float>(max - min)) - 1.0f;
}

// Reads the 12 bits coordinates given by `ds4_touch_data` into `touch`
// position.
void ReadTouchCoordinates(base::span<const uint8_t> ds4_touch_data_span,
                          GamepadTouch& touch) {
  uint16_t touch_data_x_axis =
      ((ds4_touch_data_span[1] & 0x0f) << 8) | ds4_touch_data_span[0];
  uint16_t touch_data_y_axis =
      (ds4_touch_data_span[2] << 4) | ((ds4_touch_data_span[1] & 0xf0) >> 4);

  touch.x = NormalizeTouch(touch_data_x_axis, 0, (kTouchDimensionX - 1));
  touch.y = NormalizeTouch(touch_data_y_axis, 0, (kTouchDimensionY - 1));
  touch.surface_width = kTouchDimensionX;
  touch.surface_height = kTouchDimensionY;
  touch.has_surface_dimensions = true;
}

// Reads the touchpad information given by `touchpad_data` and `touches_count`
// into `pad`.
// TODO(crbug.com/40155307): Make a member of Dualshock4Controller
template <typename Transform>
void ProcessTouchData(base::span<const TouchPadData> touchpad_data,
                      Transform& id_transform,
                      std::optional<uint32_t>& initial_touch_id,
                      Gamepad* pad) {
  pad->touch_events_length = 0;
  auto& touches = pad->touch_events;

  for (const auto& touchpad_data_entry : touchpad_data) {
    auto [touch_id_0, touch_id_1] = id_transform(
        touchpad_data_entry.touch[0].id, touchpad_data_entry.touch[1].id);
    // 2 touches per touch pad data entry
    for (auto j = 0u; j < 2; ++j) {
      auto& raw_touch = touchpad_data_entry.touch[j];

      if (!raw_touch.is_invalid) {
        if (!initial_touch_id.has_value()) {
          initial_touch_id = j == 0 ? touch_id_0 : touch_id_1;
        }
        auto& touch = touches[pad->touch_events_length++];
        touch.touch_id =
            (j == 0 ? touch_id_0 : touch_id_1) - initial_touch_id.value();
        touch.surface_id = 0;
        // x and y coordinates stored in 3 bytes (12bits each)
        ReadTouchCoordinates(raw_touch.data, touch);
      }
    }
  }
}

// Reads the Axis and button information given by |controller_data| into
// |pad|.
void ProcessAxisButtonData(const ControllerData& controller_data,
                           Gamepad* pad) {
  // Button and axis indices must match the ordering expected by the
  // Dualshock4 mapping function.
  pad->axes[0] = NormalizeAxis(controller_data.axis_left_x);
  pad->axes[1] = NormalizeAxis(controller_data.axis_left_y);
  pad->axes[2] = NormalizeAxis(controller_data.axis_right_x);
  pad->axes[3] = NormalizeAxis(controller_data.axis_left_2);
  pad->axes[4] = NormalizeAxis(controller_data.axis_right_2);
  pad->axes[5] = NormalizeAxis(controller_data.axis_right_y);
  pad->axes[9] = NormalizeDpad(controller_data.axis_dpad);
  const auto button_values = std::to_array<bool>({
      controller_data.button_square,
      controller_data.button_cross,
      controller_data.button_circle,
      controller_data.button_triangle,
      controller_data.button_left_1,
      controller_data.button_right_1,
      controller_data.button_left_2,
      controller_data.button_right_2,
      controller_data.button_share,
      controller_data.button_options,
      controller_data.button_left_3,
      controller_data.button_right_3,
      controller_data.button_ps,
      controller_data.button_touch,
  });
  for (size_t i = 0; i < std::size(button_values); ++i) {
    pad->buttons[i].pressed = button_values[i];
    pad->buttons[i].touched = button_values[i];
    pad->buttons[i].value = button_values[i] ? 1.0 : 0.0;
  }
}

}  // namespace

template <typename ExtendedType, typename BaseType>
ExtendedType
Dualshock4Controller::ExtendedCounter<ExtendedType, BaseType>::operator()(
    BaseType num,
    ExtendedCounter const* other) {
  if (other && prefix < other->prefix && last != num) {
    last = kLastMax;
    ++prefix;
  }

  auto pre = prefix;
  if (num == last && num == 127) {
    pre -= 1;
  } else if (num == 127) {
    ++prefix;
  }
  last = num;
  return (pre << 7) | num;
}

Dualshock4Controller::Dualshock4Controller(GamepadId gamepad_id,
                                           GamepadBusType bus_type,
                                           std::unique_ptr<HidWriter> writer)
    : gamepad_id_(gamepad_id),
      bus_type_(bus_type),
      writer_(std::move(writer)) {}

Dualshock4Controller::~Dualshock4Controller() = default;

// static
bool Dualshock4Controller::IsDualshock4(GamepadId gamepad_id) {
  return gamepad_id == GamepadId::kSonyProduct05c4 ||
         gamepad_id == GamepadId::kSonyProduct09cc ||
         gamepad_id == GamepadId::kScufProduct7725;
}

// static
GamepadBusType Dualshock4Controller::BusTypeFromVersionNumber(
    uint32_t version_number) {
  // RawInputDataFetcher on Windows does not distinguish devices by bus type.
  // Detect the transport in use by inspecting the version number reported by
  // the device.
  if (version_number == kDualshock4VersionUsb)
    return GAMEPAD_BUS_USB;
  if (version_number == kDualshock4VersionBluetooth)
    return GAMEPAD_BUS_BLUETOOTH;
  return GAMEPAD_BUS_UNKNOWN;
}

void Dualshock4Controller::DoShutdown() {
  writer_.reset();
}

bool Dualshock4Controller::ProcessInputReport(uint8_t report_id,
                                              base::span<const uint8_t> report,
                                              Gamepad* pad,
                                              bool ignore_button_axis,
                                              bool is_multitouch_enabled) {
  DCHECK(pad);

  const ControllerData* controller_data = nullptr;
  base::span<const TouchPadData> touches_span;
  uint8_t touches_count = 0;

  auto set_controller_and_touch_data =
      [&controller_data, &touches_span, &touches_count,
       is_multitouch_enabled](const auto& data) {
        controller_data = &data->controller_data;
        if (is_multitouch_enabled) {
          touches_span = base::span(data->touches);
          touches_count = data->touches_count;
        }
      };

  if (bus_type_ == GAMEPAD_BUS_USB &&
      report_id == kReportId01 /*USB feature report*/ &&
      report.size_bytes() >= sizeof(Dualshock4InputReportUsb) &&
      is_multitouch_enabled) {
    const auto* data =
        reinterpret_cast<const Dualshock4InputReportUsb*>(report.data());
    set_controller_and_touch_data(data);
  } else if (report_id == kReportId11 /*Bluetooth feature report*/ &&
             report.size_bytes() >= sizeof(Dualshock4InputReportBluetooth)) {
    const auto* data =
        reinterpret_cast<const Dualshock4InputReportBluetooth*>(report.data());
    set_controller_and_touch_data(data);
  } else {
    return false;
  }

  if (!ignore_button_axis) {
    ProcessAxisButtonData(*controller_data, pad);
  }

  if (is_multitouch_enabled) {
    pad->supports_touch_events_ = true;
    ProcessTouchData(touches_span.first(touches_count), transform_touch_id_,
                     initial_touch_id_, pad);
  }

  pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
  return true;
}

void Dualshock4Controller::SetVibration(
    mojom::GamepadEffectParametersPtr params) {
  // Genuine DualShock 4 gamepads use an alternate output report when
  // connected over Bluetooth. Always send USB-mode reports to SCUF Vantage
  // gamepads.
  if (bus_type_ == GAMEPAD_BUS_BLUETOOTH &&
      gamepad_id_ != GamepadId::kScufProduct7725) {
    SetVibrationBluetooth(params->strong_magnitude, params->weak_magnitude);
    return;
  }

  SetVibrationUsb(params->strong_magnitude, params->weak_magnitude);
}

void Dualshock4Controller::SetVibrationUsb(double strong_magnitude,
                                           double weak_magnitude) {
  DCHECK(writer_);
  // Construct a USB output report with report ID 0x05. In USB mode, the 0x05
  // report is used to control vibration, LEDs, and audio volume.
  // https://github.com/torvalds/linux/blob/master/drivers/hid/hid-sony.c#L2105
  std::array<uint8_t, 32> control_report;
  control_report.fill(0);
  control_report[0] = kReportId05;
  control_report[1] = 0x01;  // motor only, don't update LEDs
  control_report[4] =
      base::ClampRound<uint8_t>(weak_magnitude * kRumbleMagnitudeMax);
  control_report[5] =
      base::ClampRound<uint8_t>(strong_magnitude * kRumbleMagnitudeMax);

  writer_->WriteOutputReport(control_report);
}

void Dualshock4Controller::SetVibrationBluetooth(double strong_magnitude,
                                                 double weak_magnitude) {
  DCHECK(writer_);
  // Construct a Bluetooth output report with report ID 0x11. In Bluetooth
  // mode, the 0x11 report is used to control vibration, LEDs, and audio
  // volume. https://www.psdevwiki.com/ps4/DS4-BT#0x11_2
  std::array<uint8_t, 78> control_report;
  control_report.fill(0);
  control_report[0] = kReportId11;
  control_report[1] = 0xc0;  // unknown
  control_report[2] = 0x20;  // unknown
  control_report[3] = 0xf1;  // motor only, don't update LEDs
  control_report[4] = 0x04;  // unknown
  control_report[6] =
      base::ClampRound<uint8_t>(weak_magnitude * kRumbleMagnitudeMax);
  control_report[7] =
      base::ClampRound<uint8_t>(strong_magnitude * kRumbleMagnitudeMax);
  control_report[21] = 0x43;  // volume left
  control_report[22] = 0x43;  // volume right
  control_report[24] = 0x4d;  // volume speaker
  control_report[25] = 0x85;  // unknown

  // The last four bytes of the report hold a CRC32 checksum. Compute the
  // checksum and store it in little-endian byte order.
  uint32_t crc = ComputeDualshock4Checksum(
      base::span(control_report).first(control_report.size() - 4));
  control_report[control_report.size() - 4] = crc & 0xff;
  control_report[control_report.size() - 3] = (crc >> 8) & 0xff;
  control_report[control_report.size() - 2] = (crc >> 16) & 0xff;
  control_report[control_report.size() - 1] = (crc >> 24) & 0xff;

  writer_->WriteOutputReport(control_report);
}

base::WeakPtr<AbstractHapticGamepad> Dualshock4Controller::GetWeakPtr() {
  return weak_factory_.GetWeakPtr();
}

}  // namespace device