| // 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/switch_pro_controller_base.h" |
| |
| #include <limits> |
| |
| #include "device/gamepad/gamepad_data_fetcher.h" |
| #include "device/gamepad/gamepad_standard_mappings.h" |
| |
| namespace { |
| const uint16_t kVendorNintendo = 0x057e; |
| const uint16_t kProductSwitchProController = 0x2009; |
| |
| const uint8_t kRumbleMagnitudeMax = 0xff; |
| |
| // Switch Pro Controller USB packet types. |
| static const uint8_t kPacketTypeStatus = 0x81; |
| static const uint8_t kPacketTypeControllerData = 0x30; |
| |
| // Status packet subtypes. |
| static const uint8_t kStatusTypeSerial = 0x01; |
| static const uint8_t kStatusTypeInit = 0x02; |
| |
| // Axis extents, used for normalization. |
| static const int8_t kAxisMin = std::numeric_limits<int8_t>::min(); |
| static const int8_t kAxisMax = std::numeric_limits<int8_t>::max(); |
| |
| enum ControllerType { UNKNOWN_CONTROLLER, SWITCH_PRO_CONTROLLER }; |
| |
| #pragma pack(push, 1) |
| struct ControllerDataReport { |
| uint8_t type; // must be kPacketTypeControllerData |
| uint8_t timestamp; |
| uint8_t dummy1; |
| bool button_y : 1; |
| bool button_x : 1; |
| bool button_b : 1; |
| bool button_a : 1; |
| bool dummy2 : 2; |
| bool button_r : 1; |
| bool button_zr : 1; |
| bool button_minus : 1; |
| bool button_plus : 1; |
| bool button_thumb_r : 1; |
| bool button_thumb_l : 1; |
| bool button_home : 1; |
| bool button_capture : 1; |
| bool dummy3 : 2; |
| bool dpad_down : 1; |
| bool dpad_up : 1; |
| bool dpad_right : 1; |
| bool dpad_left : 1; |
| bool dummy4 : 2; |
| bool button_l : 1; |
| bool button_zl : 1; |
| uint8_t analog[6]; |
| }; |
| #pragma pack(pop) |
| |
| ControllerType ControllerTypeFromDeviceIds(uint16_t vendor_id, |
| uint16_t product_id) { |
| if (vendor_id == kVendorNintendo) { |
| switch (product_id) { |
| case kProductSwitchProController: |
| return SWITCH_PRO_CONTROLLER; |
| default: |
| break; |
| } |
| } |
| return UNKNOWN_CONTROLLER; |
| } |
| |
| double NormalizeAxis(int value, int min, int max) { |
| return (2.0 * (value - min) / static_cast<double>(max - min)) - 1.0; |
| } |
| |
| void UpdatePadStateFromControllerData(const ControllerDataReport& report, |
| device::Gamepad* pad) { |
| pad->buttons[device::BUTTON_INDEX_PRIMARY].pressed = report.button_b; |
| pad->buttons[device::BUTTON_INDEX_PRIMARY].value = |
| report.button_b ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_SECONDARY].pressed = report.button_a; |
| pad->buttons[device::BUTTON_INDEX_SECONDARY].value = |
| report.button_a ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_TERTIARY].pressed = report.button_y; |
| pad->buttons[device::BUTTON_INDEX_TERTIARY].value = |
| report.button_y ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_QUATERNARY].pressed = report.button_x; |
| pad->buttons[device::BUTTON_INDEX_QUATERNARY].value = |
| report.button_x ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_LEFT_SHOULDER].pressed = report.button_l; |
| pad->buttons[device::BUTTON_INDEX_LEFT_SHOULDER].value = |
| report.button_l ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_RIGHT_SHOULDER].pressed = report.button_r; |
| pad->buttons[device::BUTTON_INDEX_RIGHT_SHOULDER].value = |
| report.button_r ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_LEFT_TRIGGER].pressed = report.button_zl; |
| pad->buttons[device::BUTTON_INDEX_LEFT_TRIGGER].value = |
| report.button_zl ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_RIGHT_TRIGGER].pressed = report.button_zr; |
| pad->buttons[device::BUTTON_INDEX_RIGHT_TRIGGER].value = |
| report.button_zr ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_BACK_SELECT].pressed = report.button_minus; |
| pad->buttons[device::BUTTON_INDEX_BACK_SELECT].value = |
| report.button_minus ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_START].pressed = report.button_plus; |
| pad->buttons[device::BUTTON_INDEX_START].value = |
| report.button_plus ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_LEFT_THUMBSTICK].pressed = |
| report.button_thumb_l; |
| pad->buttons[device::BUTTON_INDEX_LEFT_THUMBSTICK].value = |
| report.button_thumb_l ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_RIGHT_THUMBSTICK].pressed = |
| report.button_thumb_r; |
| pad->buttons[device::BUTTON_INDEX_RIGHT_THUMBSTICK].value = |
| report.button_thumb_r ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_DPAD_UP].pressed = report.dpad_up; |
| pad->buttons[device::BUTTON_INDEX_DPAD_UP].value = report.dpad_up ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_DPAD_DOWN].pressed = report.dpad_down; |
| pad->buttons[device::BUTTON_INDEX_DPAD_DOWN].value = |
| report.dpad_down ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_DPAD_LEFT].pressed = report.dpad_left; |
| pad->buttons[device::BUTTON_INDEX_DPAD_LEFT].value = |
| report.dpad_left ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_DPAD_RIGHT].pressed = report.dpad_right; |
| pad->buttons[device::BUTTON_INDEX_DPAD_RIGHT].value = |
| report.dpad_right ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_META].pressed = report.button_home; |
| pad->buttons[device::BUTTON_INDEX_META].value = |
| report.button_home ? 1.0 : 0.0; |
| |
| pad->buttons[device::BUTTON_INDEX_META + 1].pressed = report.button_capture; |
| pad->buttons[device::BUTTON_INDEX_META + 1].value = |
| report.button_capture ? 1.0 : 0.0; |
| |
| int8_t axis_lx = |
| (((report.analog[1] & 0x0F) << 4) | ((report.analog[0] & 0xF0) >> 4)) + |
| 127; |
| int8_t axis_ly = report.analog[2] + 127; |
| int8_t axis_rx = |
| (((report.analog[4] & 0x0F) << 4) | ((report.analog[3] & 0xF0) >> 4)) + |
| 127; |
| int8_t axis_ry = report.analog[5] + 127; |
| pad->axes[device::AXIS_INDEX_LEFT_STICK_X] = |
| NormalizeAxis(axis_lx, kAxisMin, kAxisMax); |
| pad->axes[device::AXIS_INDEX_LEFT_STICK_Y] = |
| NormalizeAxis(-axis_ly, kAxisMin, kAxisMax); |
| pad->axes[device::AXIS_INDEX_RIGHT_STICK_X] = |
| NormalizeAxis(axis_rx, kAxisMin, kAxisMax); |
| pad->axes[device::AXIS_INDEX_RIGHT_STICK_Y] = |
| NormalizeAxis(-axis_ry, kAxisMin, kAxisMax); |
| |
| pad->buttons_length = device::BUTTON_INDEX_COUNT + 1; |
| pad->axes_length = device::AXIS_INDEX_COUNT; |
| } |
| |
| } // namespace |
| |
| namespace device { |
| |
| SwitchProControllerBase::~SwitchProControllerBase() = default; |
| |
| // static |
| bool SwitchProControllerBase::IsSwitchPro(uint16_t vendor_id, |
| uint16_t product_id) { |
| return ControllerTypeFromDeviceIds(vendor_id, product_id) != |
| UNKNOWN_CONTROLLER; |
| } |
| |
| void SwitchProControllerBase::DoShutdown() { |
| if (force_usb_hid_) |
| SendForceUsbHid(false); |
| force_usb_hid_ = false; |
| } |
| |
| void SwitchProControllerBase::ReadUsbPadState(Gamepad* pad) { |
| DCHECK(pad); |
| |
| // Consume reports until the input pipe is empty. |
| uint8_t report_bytes[kReportSize]; |
| while (true) { |
| size_t report_length = ReadInputReport(report_bytes); |
| if (report_length == 0) |
| break; |
| HandleInputReport(report_bytes, report_length, pad); |
| } |
| } |
| |
| void SwitchProControllerBase::HandleInputReport(void* report, |
| size_t report_length, |
| Gamepad* pad) { |
| DCHECK(report); |
| DCHECK_GE(report_length, 1U); |
| DCHECK(pad); |
| |
| const uint8_t* report_bytes = static_cast<uint8_t*>(report); |
| const uint8_t type = report_bytes[0]; |
| switch (type) { |
| case kPacketTypeStatus: |
| if (report_length >= 2) { |
| const uint8_t status_type = report_bytes[1]; |
| switch (status_type) { |
| case kStatusTypeSerial: |
| if (!sent_handshake_) { |
| sent_handshake_ = true; |
| SendHandshake(); |
| } |
| break; |
| case kStatusTypeInit: |
| force_usb_hid_ = true; |
| SendForceUsbHid(true); |
| break; |
| default: |
| break; |
| } |
| } |
| break; |
| case kPacketTypeControllerData: { |
| ControllerDataReport* controller_data = |
| reinterpret_cast<ControllerDataReport*>(report); |
| UpdatePadStateFromControllerData(*controller_data, pad); |
| pad->timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds(); |
| break; |
| } |
| default: |
| break; |
| } |
| } |
| |
| void SwitchProControllerBase::SendConnectionStatusQuery() { |
| // Requests the current connection status and info about the connected |
| // controller. The controller will respond with a status packet. |
| uint8_t report[kReportSize]; |
| memset(report, 0, kReportSize); |
| report[0] = 0x80; |
| report[1] = 0x01; |
| |
| WriteOutputReport(report, kReportSize); |
| } |
| |
| void SwitchProControllerBase::SendHandshake() { |
| // Sends handshaking packets over UART. This command can only be called once |
| // per session. The controller will respond with a status packet. |
| uint8_t report[kReportSize]; |
| memset(report, 0, kReportSize); |
| report[0] = 0x80; |
| report[1] = 0x02; |
| |
| WriteOutputReport(report, kReportSize); |
| } |
| |
| void SwitchProControllerBase::SendForceUsbHid(bool enable) { |
| // By default, the controller will revert to Bluetooth mode if it does not |
| // receive any USB HID commands within a timeout window. Enabling the |
| // ForceUsbHid mode forces all communication to go through USB HID and |
| // disables the timeout. |
| uint8_t report[kReportSize]; |
| memset(report, 0, kReportSize); |
| report[0] = 0x80; |
| report[1] = (enable ? 0x04 : 0x05); |
| |
| WriteOutputReport(report, kReportSize); |
| } |
| |
| void SwitchProControllerBase::SetVibration(double strong_magnitude, |
| double weak_magnitude) { |
| uint8_t strong_magnitude_scaled = |
| static_cast<uint8_t>(strong_magnitude * kRumbleMagnitudeMax); |
| uint8_t weak_magnitude_scaled = |
| static_cast<uint8_t>(weak_magnitude * kRumbleMagnitudeMax); |
| |
| uint8_t report[kReportSize]; |
| memset(report, 0, kReportSize); |
| report[0] = 0x10; |
| report[1] = static_cast<uint8_t>(counter_++ & 0x0F); |
| report[2] = 0x80; |
| report[6] = 0x80; |
| if (strong_magnitude_scaled > 0) { |
| report[2] = 0x80; |
| report[3] = 0x20; |
| report[4] = 0x62; |
| report[5] = strong_magnitude_scaled >> 2; |
| } |
| if (weak_magnitude_scaled > 0) { |
| report[6] = 0x98; |
| report[7] = 0x20; |
| report[8] = 0x62; |
| report[9] = weak_magnitude_scaled >> 2; |
| } |
| |
| WriteOutputReport(report, kReportSize); |
| } |
| |
| size_t SwitchProControllerBase::ReadInputReport(void* report) { |
| return 0; |
| } |
| |
| size_t SwitchProControllerBase::WriteOutputReport(void* report, |
| size_t report_length) { |
| return 0; |
| } |
| |
| } // namespace device |