#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 {
const uint16_t kDualshock4VersionUsb = 0x0100;
const uint16_t kDualshock4VersionBluetooth = 0;
constexpr uint8_t kReportId01 = 0x01;
constexpr uint8_t kReportId05 = 0x05;
constexpr uint8_t kReportId11 = 0x11;
const uint8_t kRumbleMagnitudeMax = 0xff;
const float kAxisMax = 255.0f;
const float kDpadMax = 7.0f;
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");
uint32_t ComputeDualshock4Checksum(base::span<const uint8_t> report_data) {
constexpr uint8_t bt_header = 0xa2;
uint32_t crc = base::Crc32(0xffffffff, base::span_from_ref(bt_header));
return ~base::Crc32(crc, report_data);
}
static float NormalizeAxis(uint8_t value) {
return (2.0f * value / kAxisMax) - 1.0f;
}
static float NormalizeDpad(uint8_t value) {
return (2.0f * value / kDpadMax) - 1.0f;
}
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;
}
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;
}
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);
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;
ReadTouchCoordinates(raw_touch.data, touch);
}
}
}
}
void ProcessAxisButtonData(const ControllerData& controller_data,
Gamepad* pad) {
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;
}
}
}
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;
bool Dualshock4Controller::IsDualshock4(GamepadId gamepad_id) {
return gamepad_id == GamepadId::kSonyProduct05c4 ||
gamepad_id == GamepadId::kSonyProduct09cc ||
gamepad_id == GamepadId::kScufProduct7725;
}
GamepadBusType Dualshock4Controller::BusTypeFromVersionNumber(
uint32_t version_number) {
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 &&
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 &&
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) {
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_);
std::array<uint8_t, 32> control_report;
control_report.fill(0);
control_report[0] = kReportId05;
control_report[1] = 0x01;
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_);
std::array<uint8_t, 78> control_report;
control_report.fill(0);
control_report[0] = kReportId11;
control_report[1] = 0xc0;
control_report[2] = 0x20;
control_report[3] = 0xf1;
control_report[4] = 0x04;
control_report[6] =
base::ClampRound<uint8_t>(weak_magnitude * kRumbleMagnitudeMax);
control_report[7] =
base::ClampRound<uint8_t>(strong_magnitude * kRumbleMagnitudeMax);
control_report[21] = 0x43;
control_report[22] = 0x43;
control_report[24] = 0x4d;
control_report[25] = 0x85;
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();
}
}