/
hid_haptic_gamepad.cc
156 lines (139 loc) · 5.91 KB
/
hid_haptic_gamepad.cc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
// 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