blob: f52fe81fe97617cbd78f9f773d9b5276a85647d5 [file] [log] [blame]
// Copyright 2018 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/hid_haptic_gamepad.h"
#include <algorithm>
#include <vector>
#include "device/gamepad/hid_writer.h"
namespace device {
namespace {
const size_t kBitsPerByte = 8;
void MagnitudeToBytes(double magnitude,
size_t report_size_bits,
uint32_t logical_min,
uint32_t logical_max,
std::vector<uint8_t>* bytes) {
DCHECK(bytes);
DCHECK_EQ(report_size_bits % kBitsPerByte, 0U);
bytes->clear();
if (logical_min >= logical_max)
return;
// If the vibration actuator on the device is only on or off, ensure it will
// be on for any non-zero vibration magnitude.
if (logical_min == 0 && logical_max == 1)
magnitude = (magnitude > 0.0) ? 1.0 : 0.0;
uint32_t scaled_magnitude = static_cast<uint32_t>(
magnitude * (logical_max - logical_min) + logical_min);
size_t remaining_bits = report_size_bits;
// Fields larger than one byte are stored in little-endian byte order.
while (remaining_bits > 0) {
bytes->push_back(scaled_magnitude & 0xff);
remaining_bits -= kBitsPerByte;
scaled_magnitude >>= kBitsPerByte;
}
}
} // namespace
// Supported HID gamepads.
HidHapticGamepad::HapticReportData kHapticReportData[] = {
// XSkills Gamecube USB adapter
{0x0b43, 0x0005, 0x00, 4, 3, 3, 1 * kBitsPerByte, 0, 1},
// Stadia controller prototype
{0x6666, 0x9401, 0x05, 5, 1, 3, 2 * kBitsPerByte, 0, 0xffff},
// Stadia controller
{0x18d1, 0x9400, 0x05, 5, 1, 3, 2 * kBitsPerByte, 0, 0xffff},
};
size_t kHapticReportDataLength = base::size(kHapticReportData);
HidHapticGamepad::HidHapticGamepad(const HapticReportData& data,
std::unique_ptr<HidWriter> writer)
: report_id_(data.report_id),
report_length_bytes_(data.report_length_bytes),
strong_offset_bytes_(data.strong_offset_bytes),
weak_offset_bytes_(data.weak_offset_bytes),
report_size_bits_(data.report_size_bits),
logical_min_(data.logical_min),
logical_max_(data.logical_max),
writer_(std::move(writer)) {}
HidHapticGamepad::~HidHapticGamepad() = default;
// static
std::unique_ptr<HidHapticGamepad> HidHapticGamepad::Create(
uint16_t vendor_id,
uint16_t product_id,
std::unique_ptr<HidWriter> writer) {
DCHECK(writer);
const auto* haptic_data = GetHapticReportData(vendor_id, product_id);
if (!haptic_data)
return nullptr;
return std::make_unique<HidHapticGamepad>(*haptic_data, std::move(writer));
}
// static
bool HidHapticGamepad::IsHidHaptic(uint16_t vendor_id, uint16_t product_id) {
const auto* begin = kHapticReportData;
const auto* end = kHapticReportData + kHapticReportDataLength;
const auto* find_it =
std::find_if(begin, end, [=](const HapticReportData& d) {
return d.vendor_id == vendor_id && d.product_id == product_id;
});
return find_it != end;
}
// static
const HidHapticGamepad::HapticReportData* HidHapticGamepad::GetHapticReportData(
uint16_t vendor_id,
uint16_t product_id) {
const auto* begin = kHapticReportData;
const auto* end = kHapticReportData + kHapticReportDataLength;
const auto* find_it =
std::find_if(begin, end, [=](const HapticReportData& d) {
return d.vendor_id == vendor_id && d.product_id == product_id;
});
return find_it == end ? nullptr : &*find_it;
}
void HidHapticGamepad::DoShutdown() {
writer_.reset();
}
void HidHapticGamepad::SetVibration(double strong_magnitude,
double weak_magnitude) {
DCHECK(writer_);
std::vector<uint8_t> control_report(report_length_bytes_);
control_report[0] = report_id_;
if (strong_offset_bytes_ == weak_offset_bytes_) {
// Single channel vibration. Combine both channels into a single magnitude.
std::vector<uint8_t> vibration_bytes;
double vibration_magnitude =
std::min(strong_magnitude + weak_magnitude, 1.0);
MagnitudeToBytes(vibration_magnitude, report_size_bits_, logical_min_,
logical_max_, &vibration_bytes);
// Vibration magnitude must not overwrite the report ID.
DCHECK(report_id_ == 0x00 || strong_offset_bytes_ > 0);
// Vibration magnitude must not overrun the report buffer.
DCHECK_LE(strong_offset_bytes_ + vibration_bytes.size(),
report_length_bytes_);
std::copy(vibration_bytes.begin(), vibration_bytes.end(),
control_report.begin() + strong_offset_bytes_);
} else {
// Dual channel vibration.
std::vector<uint8_t> left_bytes;
std::vector<uint8_t> right_bytes;
MagnitudeToBytes(strong_magnitude, report_size_bits_, logical_min_,
logical_max_, &left_bytes);
MagnitudeToBytes(weak_magnitude, report_size_bits_, logical_min_,
logical_max_, &right_bytes);
// Vibration magnitude must not overwrite the report ID.
DCHECK(report_id_ == 0x00 || strong_offset_bytes_ > 0);
DCHECK(report_id_ == 0x00 || weak_offset_bytes_ > 0);
// Vibration magnitude must not overrun the report buffer.
DCHECK_LE(strong_offset_bytes_ + left_bytes.size(), report_length_bytes_);
DCHECK_LE(weak_offset_bytes_ + right_bytes.size(), report_length_bytes_);
// The strong and weak vibration magnitude fields must not overlap.
DCHECK(strong_offset_bytes_ + left_bytes.size() <= weak_offset_bytes_ ||
weak_offset_bytes_ + right_bytes.size() <= strong_offset_bytes_);
std::copy(left_bytes.begin(), left_bytes.end(),
control_report.begin() + strong_offset_bytes_);
std::copy(right_bytes.begin(), right_bytes.end(),
control_report.begin() + weak_offset_bytes_);
}
writer_->WriteOutputReport(control_report);
}
base::WeakPtr<AbstractHapticGamepad> HidHapticGamepad::GetWeakPtr() {
return weak_factory_.GetWeakPtr();
}
} // namespace device