Skip to content

Instantly share code, notes, and snippets.

@localhostdotdev
Created March 14, 2019 13:15
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save localhostdotdev/f07d7ba7ae6cac4ea1c8976a41400d43 to your computer and use it in GitHub Desktop.
Save localhostdotdev/f07d7ba7ae6cac4ea1c8976a41400d43 to your computer and use it in GitHub Desktop.
From d014d5aea90694868859755f321c41f398bdd55c Mon Sep 17 00:00:00 2001
From: Matt Reynolds <mattreynolds@google.com>
Date: Wed, 13 Mar 2019 17:47:00 -0700
Subject: [PATCH] Improve support for Nintendo Switch gamepads
This adds support for Nintendo Switch controllers through Gamepad API:
* Switch Pro (USB and Bluetooth)
* Joy-Con L (Bluetooth)
* Joy-Con R (Bluetooth)
* Joy-Con L+R (Bluetooth)
* Charging Grip (USB)
These devices require an initialization sequence that is not performed
by the host's gamepad support or by the platform data fetcher. They also
provide calibration data that is needed in order to correctly scale the
thumbstick inputs.
This implementation replaces the experimental Switch Pro support on
Linux, which is removed in this CL.
BUG=801717
Change-Id: Ided8ec689a87a69643b4d4d82dc3b2bf04a7c912
---
diff --git a/device/BUILD.gn b/device/BUILD.gn
index 42c9f05..bf15377 100644
--- a/device/BUILD.gn
+++ b/device/BUILD.gn
@@ -369,6 +369,18 @@
"//ui/display:test_support",
]
}
+
+ # Some gamepad tests require the HID service. Only build these tests on
+ # platforms where the HID service is enabled.
+ if (!is_android && !is_fuchsia) {
+ sources += [ "gamepad/nintendo_data_fetcher_unittest.cc" ]
+
+ deps += [
+ "//services/device:test_support",
+ "//services/device/hid",
+ "//services/device/hid:mocks",
+ ]
+ }
}
source_set("usb_test_gadget") {
diff --git a/device/gamepad/BUILD.gn b/device/gamepad/BUILD.gn
index 759c76e..48e360a 100644
--- a/device/gamepad/BUILD.gn
+++ b/device/gamepad/BUILD.gn
@@ -82,10 +82,6 @@
"raw_input_data_fetcher_win.h",
"raw_input_gamepad_device_win.cc",
"raw_input_gamepad_device_win.h",
- "switch_pro_controller_base.cc",
- "switch_pro_controller_base.h",
- "switch_pro_controller_linux.cc",
- "switch_pro_controller_linux.h",
"udev_gamepad_linux.cc",
"udev_gamepad_linux.h",
"xbox_controller_mac.h",
diff --git a/device/gamepad/DEPS b/device/gamepad/DEPS
index c80012b5..1d27f4b 100644
--- a/device/gamepad/DEPS
+++ b/device/gamepad/DEPS
@@ -1,3 +1,4 @@
include_rules = [
"+jni",
+ "+services/device",
]
diff --git a/device/gamepad/gamepad_device_linux.cc b/device/gamepad/gamepad_device_linux.cc
index 35e195a..56e74bf 100644
--- a/device/gamepad/gamepad_device_linux.cc
+++ b/device/gamepad/gamepad_device_linux.cc
@@ -197,23 +197,10 @@
if (dualshock4_ || hid_haptics_)
return true;
- // Vibration is only supported over USB.
- // TODO(mattreynolds): add support for Switch Pro vibration over Bluetooth.
- if (switch_pro_)
- return bus_type_ == GAMEPAD_BUS_USB;
-
return supports_force_feedback_ && evdev_fd_ >= 0;
}
void GamepadDeviceLinux::ReadPadState(Gamepad* pad) {
- if (switch_pro_ && bus_type_ == GAMEPAD_BUS_USB) {
- // When connected over USB, the Switch Pro controller does not correctly
- // report its state over USB HID. Instead, fetch the state using the
- // device's vendor-specific USB protocol.
- switch_pro_->ReadUsbPadState(pad);
- return;
- }
-
DCHECK_GE(joydev_fd_, 0);
// Read button and axis events from the joydev device.
@@ -487,27 +474,17 @@
uint16_t vendor_id;
uint16_t product_id;
bool is_dualshock4 = false;
- bool is_switch_pro = false;
bool is_hid_haptic = false;
if (GetHidrawDevinfo(hidraw_fd_, &bus_type_, &vendor_id, &product_id)) {
is_dualshock4 =
Dualshock4ControllerLinux::IsDualshock4(vendor_id, product_id);
- is_switch_pro =
- SwitchProControllerLinux::IsSwitchPro(vendor_id, product_id);
is_hid_haptic = HidHapticGamepadLinux::IsHidHaptic(vendor_id, product_id);
- DCHECK_LE(is_dualshock4 + is_switch_pro + is_hid_haptic, 1);
+ DCHECK_LE(is_dualshock4 + is_hid_haptic, 1);
}
if (is_dualshock4 && !dualshock4_)
dualshock4_ = std::make_unique<Dualshock4ControllerLinux>(hidraw_fd_);
- if (is_switch_pro && !switch_pro_) {
- switch_pro_ = std::make_unique<SwitchProControllerLinux>(hidraw_fd_);
-
- if (bus_type_ == GAMEPAD_BUS_USB)
- switch_pro_->SendConnectionStatusQuery();
- }
-
if (is_hid_haptic && !hid_haptics_) {
hid_haptics_ =
HidHapticGamepadLinux::Create(vendor_id, product_id, hidraw_fd_);
@@ -520,9 +497,6 @@
if (dualshock4_)
dualshock4_->Shutdown();
dualshock4_.reset();
- if (switch_pro_)
- switch_pro_->Shutdown();
- switch_pro_.reset();
if (hid_haptics_)
hid_haptics_->Shutdown();
hid_haptics_.reset();
@@ -539,11 +513,6 @@
return;
}
- if (switch_pro_) {
- switch_pro_->SetVibration(strong_magnitude, weak_magnitude);
- return;
- }
-
if (hid_haptics_) {
hid_haptics_->SetVibration(strong_magnitude, weak_magnitude);
return;
@@ -576,11 +545,6 @@
return;
}
- if (switch_pro_) {
- switch_pro_->SetZeroVibration();
- return;
- }
-
if (hid_haptics_) {
hid_haptics_->SetZeroVibration();
return;
diff --git a/device/gamepad/gamepad_device_linux.h b/device/gamepad/gamepad_device_linux.h
index 6bffaff..5bf726d 100644
--- a/device/gamepad/gamepad_device_linux.h
+++ b/device/gamepad/gamepad_device_linux.h
@@ -13,7 +13,6 @@
#include "device/gamepad/dualshock4_controller_linux.h"
#include "device/gamepad/gamepad_standard_mappings.h"
#include "device/gamepad/hid_haptic_gamepad_linux.h"
-#include "device/gamepad/switch_pro_controller_linux.h"
#include "device/gamepad/udev_gamepad_linux.h"
extern "C" {
@@ -160,9 +159,6 @@
// Dualshock4 functionality, if available.
std::unique_ptr<Dualshock4ControllerLinux> dualshock4_;
- // Nintendo Switch Pro controller functionality, if available.
- std::unique_ptr<SwitchProControllerLinux> switch_pro_;
-
// A controller that uses a HID output report for vibration effects.
std::unique_ptr<HidHapticGamepadLinux> hid_haptics_;
};
diff --git a/device/gamepad/gamepad_id_list.h b/device/gamepad/gamepad_id_list.h
index f0127e0..2ea093a9 100644
--- a/device/gamepad/gamepad_id_list.h
+++ b/device/gamepad/gamepad_id_list.h
@@ -58,7 +58,10 @@
kMicrosoftProduct02ea = 0x045e02ea,
kMicrosoftProduct02fd = 0x045e02fd,
kMicrosoftProduct0719 = 0x045e0719,
+ kNintendoProduct2006 = 0x057e2006,
+ kNintendoProduct2007 = 0x057e2007,
kNintendoProduct2009 = 0x057e2009,
+ kNintendoProduct200e = 0x057e200e,
kNvidiaProduct7210 = 0x09557210,
kNvidiaProduct7214 = 0x09557214,
kPadixProduct2060 = 0x05832060,
diff --git a/device/gamepad/gamepad_platform_data_fetcher.h b/device/gamepad/gamepad_platform_data_fetcher.h
index 7a81940..03e540f 100644
--- a/device/gamepad/gamepad_platform_data_fetcher.h
+++ b/device/gamepad/gamepad_platform_data_fetcher.h
@@ -20,18 +20,15 @@
#include "device/gamepad/gamepad_platform_data_fetcher_android.h"
#elif defined(OS_WIN)
#include "device/gamepad/gamepad_platform_data_fetcher_win.h"
+#include "device/gamepad/nintendo_data_fetcher.h"
#include "device/gamepad/raw_input_data_fetcher_win.h"
#elif defined(OS_MACOSX)
#include "device/gamepad/game_controller_data_fetcher_mac.h"
#include "device/gamepad/gamepad_platform_data_fetcher_mac.h"
+#include "device/gamepad/nintendo_data_fetcher.h"
#include "device/gamepad/xbox_data_fetcher_mac.h"
#elif defined(OS_LINUX)
#include "device/gamepad/gamepad_platform_data_fetcher_linux.h"
-#endif
-
-#if !defined(OS_ANDROID)
-// NintendoDataFetcher requires the HID service, which is not implemented on
-// Android.
#include "device/gamepad/nintendo_data_fetcher.h"
#endif
@@ -52,11 +49,13 @@
manager->AddFactory(new GameControllerDataFetcherMac::Factory());
manager->AddFactory(new GamepadPlatformDataFetcherMac::Factory());
+ manager->AddFactory(new NintendoDataFetcher::Factory());
manager->AddFactory(new XboxDataFetcher::Factory());
#elif defined(OS_LINUX) && defined(USE_UDEV)
manager->AddFactory(new GamepadPlatformDataFetcherLinux::Factory());
+ manager->AddFactory(new NintendoDataFetcher::Factory());
#endif
}
diff --git a/device/gamepad/gamepad_platform_data_fetcher_linux.cc b/device/gamepad/gamepad_platform_data_fetcher_linux.cc
index 1033644c..69a456e 100644
--- a/device/gamepad/gamepad_platform_data_fetcher_linux.cc
+++ b/device/gamepad/gamepad_platform_data_fetcher_linux.cc
@@ -19,6 +19,7 @@
#include "base/trace_event/trace_event.h"
#include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/gamepad_uma.h"
+#include "device/gamepad/nintendo_controller.h"
#include "device/udev_linux/scoped_udev.h"
#include "device/udev_linux/udev_linux.h"
@@ -139,9 +140,26 @@
if (device == nullptr)
return;
+ // If the device cannot be opened, the joystick has been disconnected.
+ if (!device->OpenJoydevNode(pad_info, dev)) {
+ if (device->IsEmpty())
+ RemoveDevice(device);
+ return;
+ }
+
+ uint16_t vendor_id = device->GetVendorId();
+ uint16_t product_id = device->GetProductId();
+ if (NintendoController::IsNintendoController(vendor_id, product_id)) {
+ // Nintendo devices are handled by the Nintendo data fetcher.
+ device->CloseJoydevNode();
+ RemoveDevice(device);
+ return;
+ }
+
PadState* state = GetPadState(joydev_index);
if (!state) {
// No slot available for device, don't use.
+ device->CloseJoydevNode();
RemoveDevice(device);
return;
}
@@ -160,18 +178,9 @@
return;
}
- // If the device cannot be opened, the joystick has been disconnected.
- if (!device->OpenJoydevNode(pad_info, dev)) {
- if (device->IsEmpty())
- RemoveDevice(device);
- return;
- }
-
// Joydev uses its own internal list of device IDs to identify known gamepads.
// If the device is on our list, record it by ID. If the device is unknown,
// record that an unknown gamepad was enumerated.
- uint16_t vendor_id = device->GetVendorId();
- uint16_t product_id = device->GetProductId();
GamepadId gamepad_id =
GamepadIdList::Get().GetGamepadId(vendor_id, product_id);
if (gamepad_id == GamepadId::kUnknownGamepad)
diff --git a/device/gamepad/gamepad_platform_data_fetcher_mac.mm b/device/gamepad/gamepad_platform_data_fetcher_mac.mm
index f1d5503..ecac35d 100644
--- a/device/gamepad/gamepad_platform_data_fetcher_mac.mm
+++ b/device/gamepad/gamepad_platform_data_fetcher_mac.mm
@@ -12,6 +12,7 @@
#include "base/time/time.h"
#include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/gamepad_uma.h"
+#include "device/gamepad/nintendo_controller.h"
#import <Foundation/Foundation.h>
#include <IOKit/hid/IOHIDKeys.h>
@@ -191,6 +192,10 @@
uint16_t product_int = [product_id intValue];
uint16_t version_int = [version_number intValue];
+ // Nintendo devices are handled by the Nintendo data fetcher.
+ if (NintendoController::IsNintendoController(vendor_int, product_int))
+ return;
+
// Record the device before excluding Made for iOS gamepads. This allows us to
// recognize these devices even though the GameController API masks the vendor
// and product IDs. XInput devices are recorded elsewhere.
diff --git a/device/gamepad/gamepad_provider.cc b/device/gamepad/gamepad_provider.cc
index 94a99857..5c730fe 100644
--- a/device/gamepad/gamepad_provider.cc
+++ b/device/gamepad/gamepad_provider.cc
@@ -53,13 +53,15 @@
GamepadProvider::GamepadProvider(
GamepadConnectionChangeClient* connection_change_client,
- std::unique_ptr<GamepadDataFetcher> fetcher)
+ std::unique_ptr<GamepadDataFetcher> fetcher,
+ std::unique_ptr<base::Thread> polling_thread)
: is_paused_(true),
have_scheduled_do_poll_(false),
devices_changed_(true),
ever_had_user_gesture_(false),
sanitize_(true),
gamepad_shared_buffer_(std::make_unique<GamepadSharedBuffer>()),
+ polling_thread_(std::move(polling_thread)),
connection_change_client_(connection_change_client) {
Initialize(std::move(fetcher));
}
@@ -163,7 +165,8 @@
if (monitor)
monitor->AddDevicesChangedObserver(this);
- polling_thread_.reset(new base::Thread("Gamepad polling thread"));
+ if (!polling_thread_)
+ polling_thread_.reset(new base::Thread("Gamepad polling thread"));
#if defined(OS_LINUX)
// On Linux, the data fetcher needs to watch file descriptors, so the message
// loop needs to be a libevent loop.
diff --git a/device/gamepad/gamepad_provider.h b/device/gamepad/gamepad_provider.h
index 47e2ca1..2319efb 100644
--- a/device/gamepad/gamepad_provider.h
+++ b/device/gamepad/gamepad_provider.h
@@ -46,10 +46,11 @@
explicit GamepadProvider(
GamepadConnectionChangeClient* connection_change_client);
- // Manually specifies the data fetcher. Used for testing.
- explicit GamepadProvider(
- GamepadConnectionChangeClient* connection_change_client,
- std::unique_ptr<GamepadDataFetcher> fetcher);
+ // Manually specifies the data fetcher and polling thread. The polling thread
+ // will be created normally if |polling_thread| is nullptr. Used for testing.
+ GamepadProvider(GamepadConnectionChangeClient* connection_change_client,
+ std::unique_ptr<GamepadDataFetcher> fetcher,
+ std::unique_ptr<base::Thread> polling_thread);
~GamepadProvider() override;
diff --git a/device/gamepad/gamepad_provider_unittest.cc b/device/gamepad/gamepad_provider_unittest.cc
index 17b5280..d9877737 100644
--- a/device/gamepad/gamepad_provider_unittest.cc
+++ b/device/gamepad/gamepad_provider_unittest.cc
@@ -11,6 +11,7 @@
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/threading/platform_thread.h"
+#include "base/threading/thread.h"
#include "build/build_config.h"
#include "device/gamepad/gamepad_data_fetcher.h"
#include "device/gamepad/gamepad_test_helpers.h"
@@ -45,7 +46,8 @@
GamepadProvider* CreateProvider(const Gamepads& test_data) {
mock_data_fetcher_ = new MockGamepadDataFetcher(test_data);
provider_.reset(new GamepadProvider(
- nullptr, std::unique_ptr<GamepadDataFetcher>(mock_data_fetcher_)));
+ nullptr, std::unique_ptr<GamepadDataFetcher>(mock_data_fetcher_),
+ std::unique_ptr<base::Thread>()));
return provider_.get();
}
diff --git a/device/gamepad/gamepad_service.cc b/device/gamepad/gamepad_service.cc
index 8282f74..e77b609 100644
--- a/device/gamepad/gamepad_service.cc
+++ b/device/gamepad/gamepad_service.cc
@@ -11,6 +11,7 @@
#include "base/memory/ptr_util.h"
#include "base/memory/singleton.h"
#include "base/single_thread_task_runner.h"
+#include "base/threading/thread.h"
#include "base/threading/thread_task_runner_handle.h"
#include "device/gamepad/gamepad_consumer.h"
#include "device/gamepad/gamepad_data_fetcher.h"
@@ -32,7 +33,9 @@
GamepadService::GamepadService(
std::unique_ptr<device::GamepadDataFetcher> fetcher)
- : provider_(new device::GamepadProvider(this, std::move(fetcher))),
+ : provider_(new device::GamepadProvider(this,
+ std::move(fetcher),
+ std::unique_ptr<base::Thread>())),
main_thread_task_runner_(base::ThreadTaskRunnerHandle::Get()),
num_active_consumers_(0),
gesture_callback_pending_(false) {
@@ -59,7 +62,8 @@
void GamepadService::StartUp(
std::unique_ptr<service_manager::Connector> service_manager_connector) {
- service_manager_connector_ = std::move(service_manager_connector);
+ if (!service_manager_connector_)
+ service_manager_connector_ = std::move(service_manager_connector);
}
service_manager::Connector* GamepadService::GetConnector() {
diff --git a/device/gamepad/gamepad_service.h b/device/gamepad/gamepad_service.h
index 85feb22..91de120 100644
--- a/device/gamepad/gamepad_service.h
+++ b/device/gamepad/gamepad_service.h
@@ -44,6 +44,9 @@
// Returns the GamepadService singleton.
static GamepadService* GetInstance();
+ // Sets the GamepadService instance. Exposed for tests.
+ static void SetInstance(GamepadService*);
+
void StartUp(
std::unique_ptr<service_manager::Connector> service_manager_connector);
@@ -120,8 +123,6 @@
virtual ~GamepadService();
- static void SetInstance(GamepadService*);
-
void OnUserGesture();
void OnGamepadConnectionChange(bool connected,
diff --git a/device/gamepad/gamepad_standard_mappings_linux.cc b/device/gamepad/gamepad_standard_mappings_linux.cc
index 4cb0361..d120107 100644
--- a/device/gamepad/gamepad_standard_mappings_linux.cc
+++ b/device/gamepad/gamepad_standard_mappings_linux.cc
@@ -15,21 +15,6 @@
namespace device {
namespace {
-
-enum SwitchProButtons {
- SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
- SWITCH_PRO_BUTTON_COUNT
-};
-
-// The Switch Pro controller reports a larger logical range than the analog
-// axes are capable of, and as a result the received axis values only use about
-// 70% of the total range. We renormalize the axis values to cover the full
-// range. The axis extents were determined experimentally.
-const float kSwitchProAxisXMin = -0.7f;
-const float kSwitchProAxisXMax = 0.7f;
-const float kSwitchProAxisYMin = -0.65f;
-const float kSwitchProAxisYMax = 0.75f;
-
// The hid-sony driver in newer kernels uses an alternate mapping for Sony
// Playstation 3 and Playstation 4 gamepads than in older kernels. To allow
// applications to distinguish between the old mapping and the new mapping,
@@ -557,40 +542,33 @@
mapped->axes_length = AXIS_INDEX_COUNT;
}
-void MapperSwitchProUsb(const Gamepad& input, Gamepad* mapped) {
+void MapperSwitchJoyCon(const Gamepad& input, Gamepad* mapped) {
*mapped = input;
- mapped->axes[AXIS_INDEX_LEFT_STICK_X] = RenormalizeAndClampAxis(
- input.axes[0], kSwitchProAxisXMin, kSwitchProAxisXMax);
- mapped->axes[AXIS_INDEX_LEFT_STICK_Y] = RenormalizeAndClampAxis(
- input.axes[1], kSwitchProAxisYMin, kSwitchProAxisYMax);
- mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = RenormalizeAndClampAxis(
- input.axes[2], kSwitchProAxisXMin, kSwitchProAxisXMax);
- mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = RenormalizeAndClampAxis(
- input.axes[3], kSwitchProAxisYMin, kSwitchProAxisYMax);
+ mapped->buttons_length = BUTTON_INDEX_COUNT;
+ mapped->axes_length = 2;
+}
+void MapperSwitchPro(const Gamepad& input, Gamepad* mapped) {
+ enum SwitchProButtons {
+ SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
+ SWITCH_PRO_BUTTON_COUNT
+ };
+ *mapped = input;
mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT;
mapped->axes_length = AXIS_INDEX_COUNT;
}
-void MapperSwitchProBluetooth(const Gamepad& input, Gamepad* mapped) {
+void MapperSwitchComposite(const Gamepad& input, Gamepad* mapped) {
+ enum SwitchCompositeButtons {
+ SWITCH_COMPOSITE_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
+ SWITCH_COMPOSITE_BUTTON_LEFT_SL,
+ SWITCH_COMPOSITE_BUTTON_LEFT_SR,
+ SWITCH_COMPOSITE_BUTTON_RIGHT_SL,
+ SWITCH_COMPOSITE_BUTTON_RIGHT_SR,
+ SWITCH_COMPOSITE_BUTTON_COUNT
+ };
*mapped = input;
- mapped->buttons[BUTTON_INDEX_META] = input.buttons[12];
- mapped->buttons[SWITCH_PRO_BUTTON_CAPTURE] = input.buttons[13];
- mapped->buttons[BUTTON_INDEX_DPAD_UP] = AxisNegativeAsButton(input.axes[5]);
- mapped->buttons[BUTTON_INDEX_DPAD_DOWN] = AxisPositiveAsButton(input.axes[5]);
- mapped->buttons[BUTTON_INDEX_DPAD_LEFT] = AxisNegativeAsButton(input.axes[4]);
- mapped->buttons[BUTTON_INDEX_DPAD_RIGHT] =
- AxisPositiveAsButton(input.axes[4]);
- mapped->axes[AXIS_INDEX_LEFT_STICK_X] = RenormalizeAndClampAxis(
- input.axes[0], kSwitchProAxisXMin, kSwitchProAxisXMax);
- mapped->axes[AXIS_INDEX_LEFT_STICK_Y] = RenormalizeAndClampAxis(
- input.axes[1], kSwitchProAxisYMin, kSwitchProAxisYMax);
- mapped->axes[AXIS_INDEX_RIGHT_STICK_X] = RenormalizeAndClampAxis(
- input.axes[2], kSwitchProAxisXMin, kSwitchProAxisXMax);
- mapped->axes[AXIS_INDEX_RIGHT_STICK_Y] = RenormalizeAndClampAxis(
- input.axes[3], kSwitchProAxisYMin, kSwitchProAxisYMax);
-
- mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT;
+ mapped->buttons_length = SWITCH_COMPOSITE_BUTTON_COUNT;
mapped->axes_length = AXIS_INDEX_COUNT;
}
@@ -748,8 +726,14 @@
{GamepadId::kSonyProduct09cc, MapperDualshock4},
// Dualshock 4 USB receiver
{GamepadId::kSonyProduct0ba0, MapperDualshock4},
+ // Switch Joy-Con L
+ {GamepadId::kNintendoProduct2006, MapperSwitchJoyCon},
+ // Switch Joy-Con R
+ {GamepadId::kNintendoProduct2007, MapperSwitchJoyCon},
// Switch Pro Controller
- {GamepadId::kNintendoProduct2009, MapperSwitchProUsb},
+ {GamepadId::kNintendoProduct2009, MapperSwitchPro},
+ // Switch Charging Grip
+ {GamepadId::kNintendoProduct200e, MapperSwitchPro},
// iBuffalo Classic
{GamepadId::kPadixProduct2060, MapperIBuffalo},
// SmartJoy PLUS Adapter
@@ -820,12 +804,22 @@
mapper = MapperDualshock3SixAxisNew;
}
- // The Nintendo Switch Pro controller exposes the same product ID when
- // connected over USB or Bluetooth but communicates using different protocols.
- // In Bluetooth mode it uses standard HID, but in USB mode it uses a
- // vendor-specific protocol. Select a mapper depending on the connection type.
- if (mapper == MapperSwitchProUsb && bus_type == GAMEPAD_BUS_BLUETOOTH)
- mapper = MapperSwitchProBluetooth;
+ // The Switch Joy-Con Charging Grip allows a pair of Joy-Cons to be docked
+ // with the grip and used over USB as a single composite gamepad. The Nintendo
+ // data fetcher also allows a pair of Bluetooth-connected Joy-Cons to be used
+ // as a composite device and sets the same product ID as the Charging Grip.
+ //
+ // In both configurations, we remap the Joy-Con buttons to align with the
+ // Standard Gamepad mapping. Docking a Joy-Con in the Charging Grip makes the
+ // SL and SR buttons inaccessible.
+ //
+ // If the Joy-Cons are not docked, the SL and SR buttons are still accessible.
+ // Inspect the |bus_type| of the composite device to detect this case and use
+ // an alternate mapping function that exposes the extra buttons.
+ if (gamepad_id == GamepadId::kNintendoProduct200e &&
+ mapper == MapperSwitchPro && bus_type != GAMEPAD_BUS_USB) {
+ mapper = MapperSwitchComposite;
+ }
return mapper;
}
diff --git a/device/gamepad/gamepad_standard_mappings_mac.mm b/device/gamepad/gamepad_standard_mappings_mac.mm
index f326d9c..e247955 100644
--- a/device/gamepad/gamepad_standard_mappings_mac.mm
+++ b/device/gamepad/gamepad_standard_mappings_mac.mm
@@ -488,6 +488,36 @@
mapped->axes_length = AXIS_INDEX_COUNT;
}
+void MapperSwitchJoyCon(const Gamepad& input, Gamepad* mapped) {
+ *mapped = input;
+ mapped->buttons_length = BUTTON_INDEX_COUNT;
+ mapped->axes_length = 2;
+}
+
+void MapperSwitchPro(const Gamepad& input, Gamepad* mapped) {
+ enum SwitchProButtons {
+ SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
+ SWITCH_PRO_BUTTON_COUNT
+ };
+ *mapped = input;
+ mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT;
+ mapped->axes_length = AXIS_INDEX_COUNT;
+}
+
+void MapperSwitchComposite(const Gamepad& input, Gamepad* mapped) {
+ enum SwitchProButtons {
+ SWITCH_COMPOSITE_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
+ SWITCH_COMPOSITE_BUTTON_LEFT_SL,
+ SWITCH_COMPOSITE_BUTTON_LEFT_SR,
+ SWITCH_COMPOSITE_BUTTON_RIGHT_SL,
+ SWITCH_COMPOSITE_BUTTON_RIGHT_SR,
+ SWITCH_COMPOSITE_BUTTON_COUNT
+ };
+ *mapped = input;
+ mapped->buttons_length = SWITCH_COMPOSITE_BUTTON_COUNT;
+ mapped->axes_length = AXIS_INDEX_COUNT;
+}
+
constexpr struct MappingData {
GamepadId gamepad_id;
GamepadStandardMappingFunction function;
@@ -526,6 +556,14 @@
{GamepadId::kSonyProduct09cc, MapperDualshock4},
// Dualshock 4 USB receiver
{GamepadId::kSonyProduct0ba0, MapperDualshock4},
+ // Switch Joy-Con L
+ {GamepadId::kNintendoProduct2006, MapperSwitchJoyCon},
+ // Switch Joy-Con R
+ {GamepadId::kNintendoProduct2007, MapperSwitchJoyCon},
+ // Switch Pro Controller
+ {GamepadId::kNintendoProduct2009, MapperSwitchPro},
+ // Switch Charging Grip
+ {GamepadId::kNintendoProduct200e, MapperSwitchPro},
// iBuffalo Classic
{GamepadId::kPadixProduct2060, MapperIBuffalo},
// SmartJoy PLUS Adapter
@@ -574,7 +612,27 @@
const auto* find_it = std::find_if(begin, end, [=](const MappingData& item) {
return gamepad_id == item.gamepad_id;
});
- return (find_it == end) ? nullptr : find_it->function;
+ GamepadStandardMappingFunction mapper =
+ (find_it == end) ? nullptr : find_it->function;
+
+ // The Switch Joy-Con Charging Grip allows a pair of Joy-Cons to be docked
+ // with the grip and used over USB as a single composite gamepad. The Nintendo
+ // data fetcher also allows a pair of Bluetooth-connected Joy-Cons to be used
+ // as a composite device and sets the same product ID as the Charging Grip.
+ //
+ // In both configurations, we remap the Joy-Con buttons to align with the
+ // Standard Gamepad mapping. Docking a Joy-Con in the Charging Grip makes the
+ // SL and SR buttons inaccessible.
+ //
+ // If the Joy-Cons are not docked, the SL and SR buttons are still accessible.
+ // Inspect the |bus_type| of the composite device to detect this case and use
+ // an alternate mapping function that exposes the extra buttons.
+ if (gamepad_id == GamepadId::kNintendoProduct200e &&
+ mapper == MapperSwitchPro && bus_type != GAMEPAD_BUS_USB) {
+ mapper = MapperSwitchComposite;
+ }
+
+ return mapper;
}
} // namespace device
diff --git a/device/gamepad/gamepad_standard_mappings_win.cc b/device/gamepad/gamepad_standard_mappings_win.cc
index 1b2f1dd..aced1e3 100644
--- a/device/gamepad/gamepad_standard_mappings_win.cc
+++ b/device/gamepad/gamepad_standard_mappings_win.cc
@@ -356,6 +356,36 @@
mapped->axes_length = AXIS_INDEX_COUNT;
}
+void MapperSwitchJoyCon(const Gamepad& input, Gamepad* mapped) {
+ *mapped = input;
+ mapped->buttons_length = BUTTON_INDEX_COUNT;
+ mapped->axes_length = 2;
+}
+
+void MapperSwitchPro(const Gamepad& input, Gamepad* mapped) {
+ enum SwitchProButtons {
+ SWITCH_PRO_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
+ SWITCH_PRO_BUTTON_COUNT
+ };
+ *mapped = input;
+ mapped->buttons_length = SWITCH_PRO_BUTTON_COUNT;
+ mapped->axes_length = AXIS_INDEX_COUNT;
+}
+
+void MapperSwitchComposite(const Gamepad& input, Gamepad* mapped) {
+ enum SwitchProButtons {
+ SWITCH_COMPOSITE_BUTTON_CAPTURE = BUTTON_INDEX_COUNT,
+ SWITCH_COMPOSITE_BUTTON_LEFT_SL,
+ SWITCH_COMPOSITE_BUTTON_LEFT_SR,
+ SWITCH_COMPOSITE_BUTTON_RIGHT_SL,
+ SWITCH_COMPOSITE_BUTTON_RIGHT_SR,
+ SWITCH_COMPOSITE_BUTTON_COUNT
+ };
+ *mapped = input;
+ mapped->buttons_length = SWITCH_COMPOSITE_BUTTON_COUNT;
+ mapped->axes_length = AXIS_INDEX_COUNT;
+}
+
constexpr struct MappingData {
GamepadId gamepad_id;
GamepadStandardMappingFunction function;
@@ -374,6 +404,14 @@
{GamepadId::kSonyProduct09cc, MapperDualshock4},
// Dualshock 4 USB receiver
{GamepadId::kSonyProduct0ba0, MapperDualshock4},
+ // Switch Joy-Con L
+ {GamepadId::kNintendoProduct2006, MapperSwitchJoyCon},
+ // Switch Joy-Con R
+ {GamepadId::kNintendoProduct2007, MapperSwitchJoyCon},
+ // Switch Pro Controller
+ {GamepadId::kNintendoProduct2009, MapperSwitchPro},
+ // Switch Charging Grip
+ {GamepadId::kNintendoProduct200e, MapperSwitchPro},
// iBuffalo Classic
{GamepadId::kPadixProduct2060, MapperIBuffalo},
// Nvidia Shield gamepad (2015)
@@ -416,7 +454,27 @@
const auto* find_it = std::find_if(begin, end, [=](const MappingData& item) {
return gamepad_id == item.gamepad_id;
});
- return (find_it == end) ? nullptr : find_it->function;
+ GamepadStandardMappingFunction mapper =
+ (find_it == end) ? nullptr : find_it->function;
+
+ // The Switch Joy-Con Charging Grip allows a pair of Joy-Cons to be docked
+ // with the grip and used over USB as a single composite gamepad. The Nintendo
+ // data fetcher also allows a pair of Bluetooth-connected Joy-Cons to be used
+ // as a composite device and sets the same product ID as the Charging Grip.
+ //
+ // In both configurations, we remap the Joy-Con buttons to align with the
+ // Standard Gamepad mapping. Docking a Joy-Con in the Charging Grip makes the
+ // SL and SR buttons inaccessible.
+ //
+ // If the Joy-Cons are not docked, the SL and SR buttons are still accessible.
+ // Inspect the |bus_type| of the composite device to detect this case and use
+ // an alternate mapping function that exposes the extra buttons.
+ if (gamepad_id == GamepadId::kNintendoProduct200e &&
+ mapper == MapperSwitchPro && bus_type != GAMEPAD_BUS_USB) {
+ mapper = MapperSwitchComposite;
+ }
+
+ return mapper;
}
} // namespace device
diff --git a/device/gamepad/nintendo_controller.cc b/device/gamepad/nintendo_controller.cc
index 836e9e7..c78e0a2 100644
--- a/device/gamepad/nintendo_controller.cc
+++ b/device/gamepad/nintendo_controller.cc
@@ -4,27 +4,803 @@
#include "device/gamepad/nintendo_controller.h"
+#include <algorithm>
+#include <utility>
+
#include "base/bind.h"
+#include "base/strings/stringprintf.h"
#include "device/gamepad/gamepad_data_fetcher.h"
+#include "device/gamepad/gamepad_id_list.h"
namespace device {
namespace {
-// Device IDs for known Switch devices.
+// Device IDs for the Switch Charging Grip, also used for composite devices.
const uint16_t kVendorNintendo = 0x057e;
-const uint16_t kProductSwitchProController = 0x2009;
+const uint16_t kProductSwitchChargingGrip = 0x200e;
+
+// Maximum output report sizes, used to distinguish USB and Bluetooth.
+const size_t kSwitchProMaxOutputReportSizeBytesUsb = 63;
+const size_t kSwitchProMaxOutputReportSizeBytesBluetooth = 48;
+
+// Input report size.
+const size_t kMaxInputReportSizeBytes = 64;
// Device name for a composite Joy-Con device.
const char kProductNameSwitchCompositeDevice[] = "Joy-Con L+R";
+
+// Report IDs.
+const uint8_t kReportIdOutput01 = 0x01;
+const uint8_t kReportIdOutput10 = 0x10;
+const uint8_t kReportIdInput21 = 0x21;
+const uint8_t kReportIdInput30 = 0x30;
+const uint8_t kUsbReportIdOutput80 = 0x80;
+const uint8_t kUsbReportIdInput81 = 0x81;
+
+// Sub-types of the 0x80 output report, used for initialization.
+const uint8_t kSubTypeRequestMac = 0x01;
+const uint8_t kSubTypeHandshake = 0x02;
+const uint8_t kSubTypeBaudRate = 0x03;
+const uint8_t kSubTypeDisableUsbTimeout = 0x04;
+const uint8_t kSubTypeEnableUsbTimeout = 0x05;
+
+// UART subcommands.
+const uint8_t kSubCommandSetInputReportMode = 0x03;
+const uint8_t kSubCommandReadSpi = 0x10;
+const uint8_t kSubCommandSetPlayerLights = 0x30;
+const uint8_t kSubCommand33 = 0x33;
+const uint8_t kSubCommandSetHomeLight = 0x38;
+const uint8_t kSubCommandEnableImu = 0x40;
+const uint8_t kSubCommandSetImuSensitivity = 0x41;
+const uint8_t kSubCommandEnableVibration = 0x48;
+
+// SPI memory regions.
+const uint16_t kSpiImuCalibrationAddress = 0x6020;
+const size_t kSpiImuCalibrationSize = 24;
+const uint16_t kSpiAnalogStickCalibrationAddress = 0x603d;
+const size_t kSpiAnalogStickCalibrationSize = 18;
+const uint16_t kSpiImuHorizontalOffsetsAddress = 0x6080;
+const size_t kSpiImuHorizontalOffsetsSize = 6;
+const uint16_t kSpiAnalogStickParametersAddress = 0x6086;
+const size_t kSpiAnalogStickParametersSize = 18;
+
+// Byte index for the first byte of subcommand data in 0x80 output reports.
+const size_t kSubCommandDataOffset = 11;
+// Byte index for the first byte of SPI data in SPI read responses.
+const size_t kSpiDataOffset = 20;
+
+// Values for the |device_type| field reported in the MAC reply.
+const uint8_t kUsbDeviceTypeChargingGripNoDevice = 0x00;
+const uint8_t kUsbDeviceTypeChargingGripJoyConL = 0x01;
+const uint8_t kUsbDeviceTypeChargingGripJoyConR = 0x02;
+const uint8_t kUsbDeviceTypeProController = 0x03;
+
+const base::TimeDelta kTimeoutDuration =
+ base::TimeDelta::FromMilliseconds(3000);
+const size_t kMaxRetryCount = 3;
+
+const size_t kMaxVibrationEffectDurationMillis = 100;
+
+// Initialization parameters.
+const uint8_t kGyroSensitivity2000Dps = 0x03;
+const uint8_t kAccelerometerSensitivity8G = 0x00;
+const uint8_t kGyroPerformance208Hz = 0x01;
+const uint8_t kAccelerometerFilterBandwidth100Hz = 0x01;
+const uint8_t kPlayerLightPattern1 = 0x01;
+
+// Parameters for the "strong" and "weak" components of the dual-rumble effect.
+const double kVibrationFrequencyStrongRumble = 141.0;
+const double kVibrationFrequencyWeakRumble = 182.0;
+const double kVibrationAmplitudeStrongRumbleMax = 0.9;
+const double kVibrationAmplitudeWeakRumbleMax = 0.1;
+
+const int kVibrationFrequencyHzMin = 41;
+const int kVibrationFrequencyHzMax = 1253;
+const int kVibrationAmplitudeMax = 1000;
+
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+struct VibrationFrequency {
+ uint16_t hf;
+ uint8_t lf;
+ int freq_hz; // rounded
+} kVibrationFrequency[] = {
+ // Unused frequencies have been removed. Must be sorted.
+ {0x0068, 0x3a, 141},
+ {0x0098, 0x46, 182}};
+const size_t kVibrationFrequencySize = base::size(kVibrationFrequency);
+
+// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering/blob/master/rumble_data_table.md
+struct VibrationAmplitude {
+ uint8_t hfa;
+ uint16_t lfa;
+ int amp; // rounded, max 1000 (kVibrationAmplitudeMax)
+} kVibrationAmplitude[]{
+ // Only include safe amplitudes.
+ {0x00, 0x0040, 0}, {0x02, 0x8040, 10}, {0x04, 0x0041, 12},
+ {0x06, 0x8041, 14}, {0x08, 0x0042, 17}, {0x0a, 0x8042, 20},
+ {0x0c, 0x0043, 24}, {0x0e, 0x8043, 28}, {0x10, 0x0044, 33},
+ {0x12, 0x8044, 40}, {0x14, 0x0045, 47}, {0x16, 0x8045, 56},
+ {0x18, 0x0046, 67}, {0x1a, 0x8046, 80}, {0x1c, 0x0047, 95},
+ {0x1e, 0x8047, 112}, {0x20, 0x0048, 117}, {0x22, 0x8048, 123},
+ {0x24, 0x0049, 128}, {0x26, 0x8049, 134}, {0x28, 0x004a, 140},
+ {0x2a, 0x804a, 146}, {0x2c, 0x004b, 152}, {0x2e, 0x804b, 159},
+ {0x30, 0x004c, 166}, {0x32, 0x804c, 173}, {0x34, 0x004d, 181},
+ {0x36, 0x804d, 189}, {0x38, 0x004e, 198}, {0x3a, 0x804e, 206},
+ {0x3c, 0x004f, 215}, {0x3e, 0x804f, 225}, {0x40, 0x0050, 230},
+ {0x42, 0x8050, 235}, {0x44, 0x0051, 240}, {0x46, 0x8051, 245},
+ {0x48, 0x0052, 251}, {0x4a, 0x8052, 256}, {0x4c, 0x0053, 262},
+ {0x4e, 0x8053, 268}, {0x50, 0x0054, 273}, {0x52, 0x8054, 279},
+ {0x54, 0x0055, 286}, {0x56, 0x8055, 292}, {0x58, 0x0056, 298},
+ {0x5a, 0x8056, 305}, {0x5c, 0x0057, 311}, {0x5e, 0x8057, 318},
+ {0x60, 0x0058, 325}, {0x62, 0x8058, 332}, {0x64, 0x0059, 340},
+ {0x66, 0x8059, 347}, {0x68, 0x005a, 355}, {0x6a, 0x805a, 362},
+ {0x6c, 0x005b, 370}, {0x6e, 0x805b, 378}, {0x70, 0x005c, 387},
+ {0x72, 0x805c, 395}, {0x74, 0x005d, 404}, {0x76, 0x805d, 413},
+ {0x78, 0x005e, 422}, {0x7a, 0x805e, 431}, {0x7c, 0x005f, 440},
+ {0x7e, 0x805f, 450}, {0x80, 0x0060, 460}, {0x82, 0x8060, 470},
+ {0x84, 0x0061, 480}, {0x86, 0x8061, 491}, {0x88, 0x0062, 501},
+ {0x8a, 0x8062, 512}, {0x8c, 0x0063, 524}, {0x8e, 0x8063, 535},
+ {0x90, 0x0064, 547}, {0x92, 0x8064, 559}, {0x94, 0x0065, 571},
+ {0x96, 0x8065, 584}, {0x98, 0x0066, 596}, {0x9a, 0x8066, 609},
+ {0x9c, 0x0067, 623}, {0x9e, 0x8067, 636}, {0xa0, 0x0068, 650},
+ {0xa2, 0x8068, 665}, {0xa4, 0x0069, 679}, {0xa6, 0x8069, 694},
+ {0xa8, 0x006a, 709}, {0xaa, 0x806a, 725}, {0xac, 0x006b, 741},
+ {0xae, 0x806b, 757}, {0xb0, 0x006c, 773}, {0xb2, 0x806c, 790},
+ {0xb4, 0x006d, 808}, {0xb6, 0x806d, 825}, {0xb8, 0x006e, 843},
+ {0xba, 0x806e, 862}, {0xbc, 0x006f, 881}, {0xbe, 0x806f, 900},
+ {0xc0, 0x0070, 920}, {0xc2, 0x8070, 940}, {0xc4, 0x0071, 960},
+ {0xc6, 0x8071, 981}, {0xc8, 0x0072, 1000},
+};
+const size_t kVibrationAmplitudeSize = base::size(kVibrationAmplitude);
+
+// Define indices for the additional buttons on Switch controllers.
+enum SWITCH_PRO_BUTTON_INDICES {
+ SWITCH_PRO_BUTTON_INDEX_CAPTURE = BUTTON_INDEX_META + 1,
+ SWITCH_PRO_BUTTON_INDEX_LEFT_SL,
+ SWITCH_PRO_BUTTON_INDEX_LEFT_SR,
+ SWITCH_PRO_BUTTON_INDEX_RIGHT_SL,
+ SWITCH_PRO_BUTTON_INDEX_RIGHT_SR,
+ SWITCH_PRO_BUTTON_INDEX_COUNT
+};
+
+// Input reports with ID 0x81 are replies to commands sent during the
+// initialization sequence.
+#pragma pack(push, 1)
+struct UsbInputReport81 {
+ uint8_t subtype;
+ uint8_t data[kMaxInputReportSizeBytes - 2];
+};
+#pragma pack(pop)
+static_assert(sizeof(UsbInputReport81) == kMaxInputReportSizeBytes - 1,
+ "UsbInputReport81 has incorrect size");
+
+// When connected over USB, the initialization sequence includes a step to
+// request the MAC address. The MAC is returned in an input report with ID 0x81
+// and subtype 0x01.
+#pragma pack(push, 1)
+struct MacAddressReport {
+ uint8_t subtype; // 0x01
+ uint8_t padding;
+ uint8_t device_type;
+ uint8_t mac_data[6];
+ uint8_t padding2[kMaxInputReportSizeBytes - 10];
+};
+#pragma pack(pop)
+static_assert(sizeof(MacAddressReport) == kMaxInputReportSizeBytes - 1,
+ "MacAddressReport has incorrect size");
+
+// When configured for standard full input report mode, controller data is
+// reported at regular intervals. The data format is the same for all Switch
+// devices, although some buttons are not present on all devices.
+#pragma pack(push, 1)
+struct ControllerData {
+ uint8_t timestamp;
+ uint8_t battery_level : 4;
+ uint8_t connection_info : 4;
+ bool button_y : 1;
+ bool button_x : 1;
+ bool button_b : 1;
+ bool button_a : 1;
+ bool button_right_sr : 1;
+ bool button_right_sl : 1;
+ 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;
+ uint8_t dummy : 1;
+ bool charging_grip : 1;
+ bool dpad_down : 1;
+ bool dpad_up : 1;
+ bool dpad_right : 1;
+ bool dpad_left : 1;
+ bool button_left_sr : 1;
+ bool button_left_sl : 1;
+ bool button_l : 1;
+ bool button_zl : 1;
+ uint8_t analog[6];
+ uint8_t vibrator_input_report;
+};
+#pragma pack(pop)
+static_assert(sizeof(ControllerData) == 12,
+ "ControllerData has incorrect size");
+
+// In standard full input report mode, controller data is reported with IMU data
+// in reports with ID 0x30.
+#pragma pack(push, 1)
+struct ControllerDataReport {
+ ControllerData controller_data; // 12 bytes
+ uint8_t imu_data[36];
+ uint8_t padding[kMaxInputReportSizeBytes - 49];
+};
+#pragma pack(pop)
+static_assert(sizeof(ControllerDataReport) == kMaxInputReportSizeBytes - 1,
+ "ControllerDataReport has incorrect size");
+
+// Responses to SPI read requests are sent in reports with ID 0x21. These
+// reports also include controller data.
+#pragma pack(push, 1)
+struct SpiReadReport {
+ ControllerData controller_data; // 12 bytes
+ uint8_t subcommand_ack; // 0x90
+ uint8_t subcommand; // 0x10
+ uint8_t addrl;
+ uint8_t addrh;
+ uint8_t padding[2]; // 0x00 0x00
+ uint8_t length;
+ uint8_t spi_data[kMaxInputReportSizeBytes - kSpiDataOffset];
+};
+#pragma pack(pop)
+static_assert(sizeof(SpiReadReport) == kMaxInputReportSizeBytes - 1,
+ "SpiReadReport has incorrect size");
+
+// Unpack two packed 12-bit values.
+void UnpackShorts(uint8_t byte0,
+ uint8_t byte1,
+ uint8_t byte2,
+ uint16_t* short1,
+ uint16_t* short2) {
+ DCHECK(short1);
+ DCHECK(short2);
+ *short1 = ((byte1 << 8) & 0x0f00) | byte0;
+ *short2 = (byte2 << 4) | (byte1 >> 4);
+}
+
+// Unpack a 6-byte MAC address.
+uint64_t UnpackSwitchMacAddress(const uint8_t* data) {
+ DCHECK(data);
+ uint64_t acc = data[5];
+ acc = (acc << 8) | data[4];
+ acc = (acc << 8) | data[3];
+ acc = (acc << 8) | data[2];
+ acc = (acc << 8) | data[1];
+ acc = (acc << 8) | data[0];
+ return acc;
+}
+
+// Unpack the analog stick parameters into |cal|.
+void UnpackSwitchAnalogStickParameters(
+ const uint8_t* data,
+ NintendoController::SwitchCalibrationData& cal) {
+ DCHECK(data);
+ // Only fetch the dead zone and range ratio. The other parameters are unknown.
+ UnpackShorts(data[3], data[4], data[5], &cal.dead_zone, &cal.range_ratio);
+}
+
+// Unpack the IMU calibration data into |cal|
+void UnpackSwitchImuCalibration(
+ const uint8_t* data,
+ NintendoController::SwitchCalibrationData& cal) {
+ DCHECK(data);
+ // 24 bytes, as 4 groups of 3 16-bit little-endian values.
+ cal.accelerometer_origin_x = (data[1] << 8) | data[0];
+ cal.accelerometer_origin_y = (data[3] << 8) | data[2];
+ cal.accelerometer_origin_z = (data[5] << 8) | data[4];
+ cal.accelerometer_sensitivity_x = (data[7] << 8) | data[6];
+ cal.accelerometer_sensitivity_y = (data[9] << 8) | data[8];
+ cal.accelerometer_sensitivity_z = (data[11] << 8) | data[10];
+ cal.gyro_origin_x = (data[13] << 8) | data[12];
+ cal.gyro_origin_y = (data[15] << 8) | data[14];
+ cal.gyro_origin_z = (data[17] << 8) | data[16];
+ cal.gyro_sensitivity_x = (data[19] << 8) | data[18];
+ cal.gyro_sensitivity_y = (data[21] << 8) | data[20];
+ cal.gyro_sensitivity_z = (data[23] << 8) | data[22];
+}
+
+// Unpack the IMU horizontal offsets into |cal|.
+void UnpackSwitchImuHorizontalOffsets(
+ const uint8_t* data,
+ NintendoController::SwitchCalibrationData& cal) {
+ DCHECK(data);
+ // 6 bytes, as 3 16-bit little-endian values.
+ cal.horizontal_offset_x = (data[1] << 8) | data[0];
+ cal.horizontal_offset_y = (data[3] << 8) | data[2];
+ cal.horizontal_offset_z = (data[5] << 8) | data[4];
+}
+
+// Unpack the analog stick calibration data into |cal|.
+void UnpackSwitchAnalogStickCalibration(
+ const uint8_t* data,
+ NintendoController::SwitchCalibrationData& cal) {
+ DCHECK(data);
+ // 18 bytes, as 2 groups of 6 packed 12-bit values.
+ UnpackShorts(data[0], data[1], data[2], &cal.lx_max, &cal.ly_max);
+ UnpackShorts(data[3], data[4], data[5], &cal.lx_center, &cal.ly_center);
+ UnpackShorts(data[6], data[7], data[8], &cal.lx_min, &cal.ly_min);
+ UnpackShorts(data[9], data[10], data[11], &cal.rx_center, &cal.ry_center);
+ UnpackShorts(data[12], data[13], data[14], &cal.rx_min, &cal.ry_min);
+ UnpackShorts(data[15], data[16], data[17], &cal.rx_max, &cal.ry_max);
+ cal.lx_min = cal.lx_center - cal.lx_min;
+ cal.lx_max = cal.lx_center + cal.lx_max;
+ cal.ly_min = cal.ly_center - cal.ly_min;
+ cal.ly_max = cal.ly_center + cal.ly_max;
+ cal.rx_min = cal.rx_center - cal.rx_min;
+ cal.rx_max = cal.rx_center + cal.rx_max;
+ cal.ry_min = cal.ry_center - cal.ry_min;
+ cal.ry_max = cal.ry_center + cal.ry_max;
+}
+
+// Unpack one frame of IMU data into |imu_data|.
+void UnpackSwitchImuData(const uint8_t* data,
+ NintendoController::SwitchImuData* imu_data) {
+ DCHECK(data);
+ DCHECK(imu_data);
+ // 12 bytes of IMU data containing 6 16-bit little-endian values.
+ imu_data->accelerometer_x = (data[1] << 8) | data[0];
+ imu_data->accelerometer_y = (data[3] << 8) | data[2];
+ imu_data->accelerometer_z = (data[5] << 8) | data[4];
+ imu_data->gyro_x = (data[7] << 8) | data[6];
+ imu_data->gyro_y = (data[9] << 8) | data[8];
+ imu_data->gyro_z = (data[11] << 8) | data[10];
+}
+
+// Given joystick input |x|,|y|, apply a radial deadzone with radius
+// |dead_zone| centered at |x_center|,|y_center|. If the input is within the
+// dead zone region, the value is snapped to the center of the dead zone.
+bool ApplyDeadZone(uint16_t& x,
+ uint16_t& y,
+ uint16_t x_center,
+ uint16_t y_center,
+ uint16_t dead_zone) {
+ int dx = x - x_center;
+ int dy = y - y_center;
+ if (dx * dx + dy * dy < dead_zone * dead_zone) {
+ x = x_center;
+ y = y_center;
+ return true;
+ }
+ return false;
+}
+
+// Normalize |value| to the range [|min|,|max|]. If |value| is outside this
+// range, clamp it.
+double NormalizeAndClampAxis(int value, int min, int max) {
+ if (value <= min)
+ return -1.0;
+ if (value >= max)
+ return 1.0;
+ return (2.0 * (value - min) / static_cast<double>(max - min)) - 1.0;
+}
+
+// Update the button and axis state in |pad| with the new controller data in
+// |data|, using the calibration data |cal|. Returns true if the new data
+// differs from the previous data.
+bool UpdateGamepadFromControllerData(
+ const ControllerData& data,
+ const NintendoController::SwitchCalibrationData& cal,
+ Gamepad& pad) {
+ bool buttons_changed =
+ pad.buttons[BUTTON_INDEX_PRIMARY].pressed != data.button_b ||
+ pad.buttons[BUTTON_INDEX_SECONDARY].pressed != data.button_a ||
+ pad.buttons[BUTTON_INDEX_TERTIARY].pressed != data.button_y ||
+ pad.buttons[BUTTON_INDEX_QUATERNARY].pressed != data.button_x ||
+ pad.buttons[BUTTON_INDEX_LEFT_SHOULDER].pressed != data.button_l ||
+ pad.buttons[BUTTON_INDEX_RIGHT_SHOULDER].pressed != data.button_r ||
+ pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].pressed != data.button_zl ||
+ pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].pressed != data.button_zr ||
+ pad.buttons[BUTTON_INDEX_BACK_SELECT].pressed != data.button_minus ||
+ pad.buttons[BUTTON_INDEX_START].pressed != data.button_plus ||
+ pad.buttons[BUTTON_INDEX_LEFT_THUMBSTICK].pressed !=
+ data.button_thumb_l ||
+ pad.buttons[BUTTON_INDEX_RIGHT_THUMBSTICK].pressed !=
+ data.button_thumb_r ||
+ pad.buttons[BUTTON_INDEX_DPAD_UP].pressed != data.dpad_up ||
+ pad.buttons[BUTTON_INDEX_DPAD_DOWN].pressed != data.dpad_down ||
+ pad.buttons[BUTTON_INDEX_DPAD_LEFT].pressed != data.dpad_left ||
+ pad.buttons[BUTTON_INDEX_DPAD_RIGHT].pressed != data.dpad_right ||
+ pad.buttons[BUTTON_INDEX_META].pressed != data.button_home ||
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_CAPTURE].pressed !=
+ data.button_capture ||
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_LEFT_SL].pressed !=
+ data.button_left_sl ||
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_LEFT_SR].pressed !=
+ data.button_left_sr ||
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_RIGHT_SL].pressed !=
+ data.button_right_sl ||
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_RIGHT_SR].pressed !=
+ data.button_right_sr;
+
+ if (buttons_changed) {
+ pad.buttons[BUTTON_INDEX_PRIMARY].pressed = data.button_b;
+ pad.buttons[BUTTON_INDEX_PRIMARY].value = data.button_b ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_SECONDARY].pressed = data.button_a;
+ pad.buttons[BUTTON_INDEX_SECONDARY].value = data.button_a ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_TERTIARY].pressed = data.button_y;
+ pad.buttons[BUTTON_INDEX_TERTIARY].value = data.button_y ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_QUATERNARY].pressed = data.button_x;
+ pad.buttons[BUTTON_INDEX_QUATERNARY].value = data.button_x ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_LEFT_SHOULDER].pressed = data.button_l;
+ pad.buttons[BUTTON_INDEX_LEFT_SHOULDER].value = data.button_l ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_RIGHT_SHOULDER].pressed = data.button_r;
+ pad.buttons[BUTTON_INDEX_RIGHT_SHOULDER].value = data.button_r ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].pressed = data.button_zl;
+ pad.buttons[BUTTON_INDEX_LEFT_TRIGGER].value = data.button_zl ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].pressed = data.button_zr;
+ pad.buttons[BUTTON_INDEX_RIGHT_TRIGGER].value = data.button_zr ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_BACK_SELECT].pressed = data.button_minus;
+ pad.buttons[BUTTON_INDEX_BACK_SELECT].value = data.button_minus ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_START].pressed = data.button_plus;
+ pad.buttons[BUTTON_INDEX_START].value = data.button_plus ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_LEFT_THUMBSTICK].pressed = data.button_thumb_l;
+ pad.buttons[BUTTON_INDEX_LEFT_THUMBSTICK].value =
+ data.button_thumb_l ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_RIGHT_THUMBSTICK].pressed = data.button_thumb_r;
+ pad.buttons[BUTTON_INDEX_RIGHT_THUMBSTICK].value =
+ data.button_thumb_r ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_DPAD_UP].pressed = data.dpad_up;
+ pad.buttons[BUTTON_INDEX_DPAD_UP].value = data.dpad_up ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_DPAD_DOWN].pressed = data.dpad_down;
+ pad.buttons[BUTTON_INDEX_DPAD_DOWN].value = data.dpad_down ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_DPAD_LEFT].pressed = data.dpad_left;
+ pad.buttons[BUTTON_INDEX_DPAD_LEFT].value = data.dpad_left ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_DPAD_RIGHT].pressed = data.dpad_right;
+ pad.buttons[BUTTON_INDEX_DPAD_RIGHT].value = data.dpad_right ? 1.0 : 0.0;
+ pad.buttons[BUTTON_INDEX_META].pressed = data.button_home;
+ pad.buttons[BUTTON_INDEX_META].value = data.button_home ? 1.0 : 0.0;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_CAPTURE].pressed = data.button_capture;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_CAPTURE].value =
+ data.button_capture ? 1.0 : 0.0;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_LEFT_SL].pressed = data.button_left_sl;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_LEFT_SL].value =
+ data.button_left_sl ? 1.0 : 0.0;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_LEFT_SR].pressed = data.button_left_sr;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_LEFT_SR].value =
+ data.button_left_sr ? 1.0 : 0.0;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_RIGHT_SL].pressed =
+ data.button_right_sl;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_RIGHT_SL].value =
+ data.button_right_sl ? 1.0 : 0.0;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_RIGHT_SR].pressed =
+ data.button_right_sr;
+ pad.buttons[SWITCH_PRO_BUTTON_INDEX_RIGHT_SR].value =
+ data.button_right_sr ? 1.0 : 0.0;
+ }
+
+ uint16_t axis_lx;
+ uint16_t axis_ly;
+ uint16_t axis_rx;
+ uint16_t axis_ry;
+ UnpackShorts(data.analog[0], data.analog[1], data.analog[2], &axis_lx,
+ &axis_ly);
+ UnpackShorts(data.analog[3], data.analog[4], data.analog[5], &axis_rx,
+ &axis_ry);
+ // Apply a radial dead zone to both sticks.
+ bool ldead = ApplyDeadZone(axis_lx, axis_ly, cal.lx_center, cal.ly_center,
+ cal.dead_zone);
+ bool rdead = ApplyDeadZone(axis_rx, axis_ry, cal.rx_center, cal.ry_center,
+ cal.dead_zone);
+ // Normalize using calibration data.
+ double lx =
+ ldead ? 0.0 : NormalizeAndClampAxis(axis_lx, cal.lx_min, cal.lx_max);
+ double ly =
+ ldead ? 0.0 : -NormalizeAndClampAxis(axis_ly, cal.ly_min, cal.ly_max);
+ double rx =
+ rdead ? 0.0 : NormalizeAndClampAxis(axis_rx, cal.rx_min, cal.rx_max);
+ double ry =
+ rdead ? 0.0 : -NormalizeAndClampAxis(axis_ry, cal.ry_min, cal.ry_max);
+ bool axes_changed = pad.axes[device::AXIS_INDEX_LEFT_STICK_X] != lx ||
+ pad.axes[device::AXIS_INDEX_LEFT_STICK_Y] != ly ||
+ pad.axes[device::AXIS_INDEX_RIGHT_STICK_X] != rx ||
+ pad.axes[device::AXIS_INDEX_RIGHT_STICK_Y] != ry;
+ if (axes_changed) {
+ pad.axes[device::AXIS_INDEX_LEFT_STICK_X] = lx;
+ pad.axes[device::AXIS_INDEX_LEFT_STICK_Y] = ly;
+ pad.axes[device::AXIS_INDEX_RIGHT_STICK_X] = rx;
+ pad.axes[device::AXIS_INDEX_RIGHT_STICK_Y] = ry;
+ }
+
+ return buttons_changed || axes_changed;
+}
+
+// Update the state for a single button. The button state is taken from
+// the button at index |button_index| in |src_pad|. If this is a composite
+// device, |src_pad| holds the state for the left component. If |horizontal| is
+// true, the button index is remapped for horizontal orientation before updating
+// the state in |dst_pad|.
+void UpdateButtonForLeftSide(const Gamepad& src_pad,
+ Gamepad& dst_pad,
+ size_t button_index,
+ bool horizontal) {
+ size_t remapped_index = button_index;
+ // The internal button mapping assumes a docked orientation for Joy-Cons. If
+ // a Joy-Con is used by itself, remap the buttons so they match the Standard
+ // Gamepad spec when held horizontally.
+ if (horizontal) {
+ switch (button_index) {
+ // Map the D-pad buttons to action buttons.
+ case BUTTON_INDEX_DPAD_LEFT:
+ remapped_index = BUTTON_INDEX_PRIMARY;
+ break;
+ case BUTTON_INDEX_DPAD_DOWN:
+ remapped_index = BUTTON_INDEX_SECONDARY;
+ break;
+ case BUTTON_INDEX_DPAD_UP:
+ remapped_index = BUTTON_INDEX_TERTIARY;
+ break;
+ case BUTTON_INDEX_DPAD_RIGHT:
+ remapped_index = BUTTON_INDEX_QUATERNARY;
+ break;
+ // Map L to Select.
+ case BUTTON_INDEX_LEFT_SHOULDER:
+ remapped_index = BUTTON_INDEX_BACK_SELECT;
+ break;
+ // Map Minus to Start.
+ case BUTTON_INDEX_BACK_SELECT:
+ remapped_index = BUTTON_INDEX_START;
+ break;
+ // Map Capture to Meta.
+ case SWITCH_PRO_BUTTON_INDEX_CAPTURE:
+ remapped_index = BUTTON_INDEX_META;
+ break;
+ // Map SL and SR to the left and right shoulders.
+ case SWITCH_PRO_BUTTON_INDEX_LEFT_SL:
+ remapped_index = BUTTON_INDEX_LEFT_SHOULDER;
+ break;
+ case SWITCH_PRO_BUTTON_INDEX_LEFT_SR:
+ remapped_index = BUTTON_INDEX_RIGHT_SHOULDER;
+ break;
+ // ZL and the left thumbstick are unmodified.
+ case BUTTON_INDEX_LEFT_TRIGGER:
+ case BUTTON_INDEX_LEFT_THUMBSTICK:
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ dst_pad.buttons[remapped_index] = src_pad.buttons[button_index];
+}
+
+// Update the state for a single button. The button state is taken from
+// the button at index |button_index| in |src_pad|. If this is a composite
+// device, |src_pad| holds the state for the right component. If |horizontal| is
+// true, the button index is remapped for horizontal orientation before updating
+// the state in |dst_pad|.
+void UpdateButtonForRightSide(const Gamepad& src_pad,
+ Gamepad& dst_pad,
+ size_t button_index,
+ bool horizontal) {
+ size_t remapped_index = button_index;
+ // The internal button mapping assumes a docked orientation for Joy-Cons. If
+ // a Joy-Con is used by itself, remap the buttons so they match the Standard
+ // Gamepad spec when held horizontally.
+ if (horizontal) {
+ switch (button_index) {
+ // Re-map the action buttons to rotate them.
+ case BUTTON_INDEX_PRIMARY:
+ remapped_index = BUTTON_INDEX_TERTIARY;
+ break;
+ case BUTTON_INDEX_TERTIARY:
+ remapped_index = BUTTON_INDEX_QUATERNARY;
+ break;
+ case BUTTON_INDEX_QUATERNARY:
+ remapped_index = BUTTON_INDEX_SECONDARY;
+ break;
+ case BUTTON_INDEX_SECONDARY:
+ remapped_index = BUTTON_INDEX_PRIMARY;
+ break;
+ // Map R to Select.
+ case BUTTON_INDEX_RIGHT_SHOULDER:
+ remapped_index = BUTTON_INDEX_BACK_SELECT;
+ break;
+ // Map SL and SR to the left and right shoulders.
+ case SWITCH_PRO_BUTTON_INDEX_RIGHT_SL:
+ remapped_index = BUTTON_INDEX_LEFT_SHOULDER;
+ break;
+ case SWITCH_PRO_BUTTON_INDEX_RIGHT_SR:
+ remapped_index = BUTTON_INDEX_RIGHT_SHOULDER;
+ break;
+ // Map right thumbstick button to left thumbstick button.
+ case BUTTON_INDEX_RIGHT_THUMBSTICK:
+ remapped_index = BUTTON_INDEX_LEFT_THUMBSTICK;
+ break;
+ // The Plus, Home, and ZR buttons are unmodified.
+ case BUTTON_INDEX_START:
+ case BUTTON_INDEX_META:
+ case BUTTON_INDEX_RIGHT_TRIGGER:
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ dst_pad.buttons[remapped_index] = src_pad.buttons[button_index];
+}
+
+// Update the state for a single axis. The axis state is taken from the axis at
+// index |axis_index| in |src_pad|. If this is a composite device, |src_pad|
+// holds the state for the left component. If |horizontal| is true, the axis
+// index and value are remapped for horizontal orientation before updating the
+// state in |dst_pad|.
+void UpdateAxisForLeftSide(const Gamepad& src_pad,
+ Gamepad& dst_pad,
+ size_t axis_index,
+ bool horizontal) {
+ size_t remapped_index = axis_index;
+ double axis_value = src_pad.axes[axis_index];
+ // The internal axis values assume a docked orientation for Joy-Cons. If a
+ // Joy-Con is used by itself, remap the axis indices and adjust the sign on
+ // the axis value for a horizontal orientation.
+ if (horizontal) {
+ switch (axis_index) {
+ case AXIS_INDEX_LEFT_STICK_X:
+ // Map +X to -Y.
+ axis_value = -axis_value;
+ remapped_index = AXIS_INDEX_LEFT_STICK_Y;
+ break;
+ case AXIS_INDEX_LEFT_STICK_Y:
+ // Map +Y to +X.
+ remapped_index = AXIS_INDEX_LEFT_STICK_X;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ dst_pad.axes[remapped_index] = axis_value;
+}
+
+// Update the state for a single axis. The axis state is taken from the axis at
+// index |axis_index| in |src_pad|. If this is a composite device, |src_pad|
+// holds the state for the right component. If |horizontal| is true, the axis
+// index and value are remapped for horizontal orientation before updating the
+// state in |dst_pad|.
+void UpdateAxisForRightSide(const Gamepad& src_pad,
+ Gamepad& dst_pad,
+ size_t axis_index,
+ bool horizontal) {
+ size_t remapped_index = axis_index;
+ double axis_value = src_pad.axes[axis_index];
+ // The internal axis values assume a docked orientation for Joy-Cons. If a
+ // Joy-Con is used by itself, remap the axis indices and adjust the sign on
+ // the axis value for a horizontal orientation.
+ if (horizontal) {
+ switch (axis_index) {
+ case AXIS_INDEX_RIGHT_STICK_X:
+ // Map +X to +Y.
+ remapped_index = AXIS_INDEX_LEFT_STICK_Y;
+ break;
+ case AXIS_INDEX_RIGHT_STICK_Y:
+ // Map +Y to -X.
+ axis_value = -axis_value;
+ remapped_index = AXIS_INDEX_LEFT_STICK_X;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+ dst_pad.axes[remapped_index] = axis_value;
+}
+
+// Convert the vibration parameters |frequency| and |amplitude| into a set of
+// parameters that can be sent to the vibration actuator.
+void FrequencyToHex(float frequency,
+ float amplitude,
+ uint16_t* hf,
+ uint8_t* lf,
+ uint8_t* hf_amp,
+ uint16_t* lf_amp) {
+ int freq = static_cast<int>(frequency);
+ int amp = static_cast<int>(amplitude * kVibrationAmplitudeMax);
+ // Clamp the target frequency and amplitude to a safe range.
+ freq = std::min(std::max(freq, kVibrationFrequencyHzMin),
+ kVibrationFrequencyHzMax);
+ amp = std::min(std::max(amp, 0), kVibrationAmplitudeMax);
+ const auto* best_vf = &kVibrationFrequency[0];
+ for (size_t i = 1; i < kVibrationFrequencySize; ++i) {
+ const auto* vf = &kVibrationFrequency[i];
+ if (!best_vf || vf->freq_hz < freq ||
+ vf->freq_hz - freq < freq - best_vf->freq_hz) {
+ best_vf = vf;
+ }
+ if (vf->freq_hz >= freq)
+ break;
+ }
+ const auto* best_va = &kVibrationAmplitude[0];
+ for (size_t i = 0; i < kVibrationAmplitudeSize; ++i) {
+ const auto* va = &kVibrationAmplitude[i];
+ if (!best_va || va->amp < amp || va->amp - amp < amp - best_va->amp)
+ best_va = va;
+ if (va->amp >= amp)
+ break;
+ }
+ DCHECK(best_vf);
+ DCHECK(best_va);
+ *hf = best_vf->hf;
+ *lf = best_vf->lf;
+ *hf_amp = best_va->hfa;
+ *lf_amp = best_va->lfa;
+}
+
+// Return the bus type of the Switch device described by |device_info|. This is
+// needed for Windows which does not report the bus type in the HID API.
+GamepadBusType BusTypeFromDeviceInfo(const mojom::HidDeviceInfo* device_info) {
+ DCHECK(device_info);
+ // If the |device_info| indicates the device is connected over Bluetooth, it's
+ // probably right. On some platforms the bus type is reported as USB
+ // regardless of the actual connection.
+ if (device_info->bus_type == mojom::HidBusType::kHIDBusTypeBluetooth)
+ return GAMEPAD_BUS_BLUETOOTH;
+ auto gamepad_id = GamepadIdList::Get().GetGamepadId(device_info->vendor_id,
+ device_info->product_id);
+ switch (gamepad_id) {
+ case GamepadId::kNintendoProduct2009:
+ // The Switch Pro Controller may be connected over USB or Bluetooth.
+ // Determine which connection is in use by comparing the max output report
+ // size against known values.
+ switch (device_info->max_output_report_size) {
+ case kSwitchProMaxOutputReportSizeBytesUsb:
+ return GAMEPAD_BUS_USB;
+ case kSwitchProMaxOutputReportSizeBytesBluetooth:
+ return GAMEPAD_BUS_BLUETOOTH;
+ default:
+ break;
+ }
+ break;
+ case GamepadId::kNintendoProduct200e:
+ // The Charging Grip can only be connected over USB.
+ return GAMEPAD_BUS_USB;
+ case GamepadId::kNintendoProduct2006:
+ case GamepadId::kNintendoProduct2007:
+ // Joy Cons can only be connected over Bluetooth. When connected through
+ // a Charging Grip, the grip's ID is reported instead.
+ return GAMEPAD_BUS_BLUETOOTH;
+ default:
+ break;
+ }
+ NOTREACHED();
+ return GAMEPAD_BUS_UNKNOWN;
+}
} // namespace
+NintendoController::SwitchCalibrationData::SwitchCalibrationData() = default;
+NintendoController::SwitchCalibrationData::~SwitchCalibrationData() = default;
+
+NintendoController::SwitchImuData::SwitchImuData() = default;
+NintendoController::SwitchImuData::~SwitchImuData() = default;
+
NintendoController::NintendoController(int source_id,
mojom::HidDeviceInfoPtr device_info,
mojom::HidManager* hid_manager)
: source_id_(source_id),
is_composite_(false),
+ bus_type_(GAMEPAD_BUS_UNKNOWN),
+ output_report_size_bytes_(0),
device_info_(std::move(device_info)),
hid_manager_(hid_manager),
- weak_factory_(this) {}
+ weak_factory_(this) {
+ if (device_info_) {
+ bus_type_ = BusTypeFromDeviceInfo(device_info_.get());
+ output_report_size_bytes_ = device_info_->max_output_report_size;
+ gamepad_id_ = GamepadIdList::Get().GetGamepadId(device_info_->vendor_id,
+ device_info_->product_id);
+ } else {
+ gamepad_id_ = GamepadId::kUnknownGamepad;
+ }
+}
NintendoController::NintendoController(
int source_id,
@@ -45,6 +821,8 @@
composite_left_.swap(composite_right_);
DCHECK_EQ(composite_left_->GetGamepadHand(), GamepadHand::kLeft);
DCHECK_EQ(composite_right_->GetGamepadHand(), GamepadHand::kRight);
+ DCHECK_EQ(composite_left_->GetBusType(), composite_right_->GetBusType());
+ bus_type_ = composite_left_->GetBusType();
}
NintendoController::~NintendoController() = default;
@@ -71,7 +849,16 @@
// static
bool NintendoController::IsNintendoController(uint16_t vendor_id,
uint16_t product_id) {
- // TODO(mattreynolds): Recognize Nintendo Switch devices.
+ auto gamepad_id = GamepadIdList::Get().GetGamepadId(vendor_id, product_id);
+ switch (gamepad_id) {
+ case GamepadId::kNintendoProduct2006:
+ case GamepadId::kNintendoProduct2007:
+ case GamepadId::kNintendoProduct2009:
+ case GamepadId::kNintendoProduct200e:
+ return true;
+ default:
+ break;
+ }
return false;
}
@@ -100,13 +887,61 @@
}
GamepadHand NintendoController::GetGamepadHand() const {
- // TODO(mattreynolds): Determine device handedness.
+ if (is_composite_)
+ return GamepadHand::kNone;
+ switch (gamepad_id_) {
+ case GamepadId::kNintendoProduct2009:
+ // Switch Pro is held in both hands.
+ return GamepadHand::kNone;
+ case GamepadId::kNintendoProduct2006:
+ // Joy-Con L is held in the left hand.
+ return GamepadHand::kLeft;
+ case GamepadId::kNintendoProduct2007:
+ // Joy-Con R is held in the right hand.
+ return GamepadHand::kRight;
+ case GamepadId::kNintendoProduct200e:
+ // Refer to |usb_device_type_| to determine the handedness of Joy-Cons
+ // connected to a Charging Grip.
+ if (state_ == kInitialized) {
+ switch (usb_device_type_) {
+ case kUsbDeviceTypeChargingGripJoyConL:
+ return GamepadHand::kLeft;
+ case kUsbDeviceTypeChargingGripJoyConR:
+ return GamepadHand::kRight;
+ case kUsbDeviceTypeChargingGripNoDevice:
+ case kUsbDeviceTypeProController:
+ return GamepadHand::kNone;
+ default:
+ break;
+ }
+ } else {
+ return GamepadHand::kNone;
+ }
+ break;
+ default:
+ break;
+ }
+ NOTREACHED();
return GamepadHand::kNone;
}
bool NintendoController::IsUsable() const {
- // TODO(mattreynolds): Determine if a device is usable.
- NOTIMPLEMENTED();
+ if (state_ != kInitialized)
+ return false;
+ if (is_composite_)
+ return true;
+ switch (gamepad_id_) {
+ case GamepadId::kNintendoProduct2009:
+ case GamepadId::kNintendoProduct2006:
+ case GamepadId::kNintendoProduct2007:
+ return true;
+ case GamepadId::kNintendoProduct200e:
+ // Only usable as a composite device.
+ return false;
+ default:
+ break;
+ }
+ NOTREACHED();
return false;
}
@@ -119,11 +954,10 @@
GamepadStandardMappingFunction NintendoController::GetMappingFunction() const {
if (is_composite_) {
- // In composite mode, we use the same mapping as a Pro Controller.
+ // In composite mode, we use the same mapping as the Charging Grip.
return GetGamepadStandardMappingFunction(
- kVendorNintendo, kProductSwitchProController, 0, bus_type_);
+ kVendorNintendo, kProductSwitchChargingGrip, 0, bus_type_);
} else {
- // Version number is not available through the HID service.
return GetGamepadStandardMappingFunction(
device_info_->vendor_id, device_info_->product_id, 0, bus_type_);
}
@@ -131,17 +965,16 @@
void NintendoController::InitializeGamepadState(bool has_standard_mapping,
Gamepad& pad) const {
- pad.buttons_length = device::BUTTON_INDEX_COUNT + 1;
+ pad.buttons_length = SWITCH_PRO_BUTTON_INDEX_COUNT;
pad.axes_length = device::AXIS_INDEX_COUNT;
pad.vibration_actuator.type = GamepadHapticActuatorType::kDualRumble;
pad.vibration_actuator.not_null = true;
pad.timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
if (is_composite_) {
- // Composite devices use the same product ID as the Switch Pro Controller,
- // and expose the same buttons and axes.
+ // Composite devices use the same product ID as the Switch Charging Grip.
GamepadDataFetcher::UpdateGamepadStrings(
kProductNameSwitchCompositeDevice, kVendorNintendo,
- kProductSwitchProController, has_standard_mapping, pad);
+ kProductSwitchChargingGrip, has_standard_mapping, pad);
} else {
GamepadDataFetcher::UpdateGamepadStrings(
device_info_->product_name, device_info_->vendor_id,
@@ -149,9 +982,139 @@
}
}
+void NintendoController::UpdatePadConnected() {
+ if (is_composite_) {
+ pad_.connected = true;
+ } else {
+ switch (gamepad_id_) {
+ case GamepadId::kNintendoProduct200e:
+ if (state_ == kInitialized) {
+ switch (usb_device_type_) {
+ case kUsbDeviceTypeChargingGripNoDevice:
+ pad_.connected = false;
+ break;
+ case kUsbDeviceTypeChargingGripJoyConL:
+ case kUsbDeviceTypeChargingGripJoyConR:
+ pad_.connected = true;
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ } else {
+ pad_.connected = false;
+ }
+ break;
+ case GamepadId::kNintendoProduct2006:
+ case GamepadId::kNintendoProduct2007:
+ case GamepadId::kNintendoProduct2009:
+ pad_.connected = (state_ == kInitialized);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ }
+}
+
void NintendoController::UpdateGamepadState(Gamepad& pad) const {
- // TODO(mattreynolds): Read gamepad state.
- NOTIMPLEMENTED();
+ if (is_composite_) {
+ // If this is a composite device, update the gamepad state using the state
+ // of the subcomponents.
+ pad.connected = true;
+ composite_left_->UpdateLeftGamepadState(pad, false);
+ composite_right_->UpdateRightGamepadState(pad, false);
+ } else {
+ switch (GetGamepadHand()) {
+ case GamepadHand::kLeft:
+ // Update state for a Joy-Con L, remapping buttons and axes to match the
+ // Standard Gamepad when the device is held horizontally.
+ UpdateLeftGamepadState(pad, true);
+ break;
+ case GamepadHand::kRight:
+ // Update state for a Joy-Con R, remapping buttons and axes to match the
+ // Standard Gamepad when the device is held horizontally.
+ UpdateRightGamepadState(pad, true);
+ break;
+ case GamepadHand::kNone:
+ // Update state for a Pro Controller.
+ UpdateLeftGamepadState(pad, false);
+ UpdateRightGamepadState(pad, false);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+ pad.connected = pad_.connected;
+ }
+}
+
+void NintendoController::UpdateLeftGamepadState(Gamepad& pad,
+ bool horizontal) const {
+ // Buttons associated with the left Joy-Con.
+ const size_t kLeftButtonIndices[] = {
+ BUTTON_INDEX_LEFT_SHOULDER, // ZL button
+ BUTTON_INDEX_LEFT_TRIGGER, // L button
+ BUTTON_INDEX_BACK_SELECT, // - button
+ BUTTON_INDEX_LEFT_THUMBSTICK,
+ BUTTON_INDEX_DPAD_UP, // D-pad directions for the composite gamepad
+ BUTTON_INDEX_DPAD_DOWN, // assume the Joy-Con is held in the vertical
+ BUTTON_INDEX_DPAD_LEFT, // orientation or is attached to a grip.
+ BUTTON_INDEX_DPAD_RIGHT,
+ SWITCH_PRO_BUTTON_INDEX_CAPTURE,
+ SWITCH_PRO_BUTTON_INDEX_LEFT_SL,
+ SWITCH_PRO_BUTTON_INDEX_LEFT_SR,
+ };
+ const size_t kLeftButtonIndicesSize = base::size(kLeftButtonIndices);
+
+ // Axes associated with the left Joy-Con thumbstick.
+ const size_t kLeftAxisIndices[] = {
+ AXIS_INDEX_LEFT_STICK_X, // Axes assume the Joy-Con is held vertically
+ AXIS_INDEX_LEFT_STICK_Y, // or is attached to a grip.
+ };
+ const size_t kLeftAxisIndicesSize = base::size(kLeftAxisIndices);
+
+ for (size_t i = 0; i < kLeftButtonIndicesSize; ++i)
+ UpdateButtonForLeftSide(pad_, pad, kLeftButtonIndices[i], horizontal);
+ for (size_t i = 0; i < kLeftAxisIndicesSize; ++i)
+ UpdateAxisForLeftSide(pad_, pad, kLeftAxisIndices[i], horizontal);
+ pad.timestamp = std::max(pad.timestamp, pad_.timestamp);
+ if (!pad_.connected)
+ pad.connected = false;
+}
+
+void NintendoController::UpdateRightGamepadState(Gamepad& pad,
+ bool horizontal) const {
+ // Buttons associated with the right Joy-Con.
+ const size_t kRightButtonIndices[]{
+ BUTTON_INDEX_PRIMARY, // B button
+ BUTTON_INDEX_SECONDARY, // A button
+ BUTTON_INDEX_TERTIARY, // Y button
+ BUTTON_INDEX_QUATERNARY, // X button
+ BUTTON_INDEX_RIGHT_SHOULDER, // R button
+ BUTTON_INDEX_RIGHT_TRIGGER, // ZR button
+ BUTTON_INDEX_START, // + button
+ BUTTON_INDEX_RIGHT_THUMBSTICK,
+ BUTTON_INDEX_META, // Home button
+ SWITCH_PRO_BUTTON_INDEX_RIGHT_SL,
+ SWITCH_PRO_BUTTON_INDEX_RIGHT_SR,
+ };
+ const size_t kRightButtonIndicesSize = base::size(kRightButtonIndices);
+
+ // Axes associated with the right Joy-Con thumbstick.
+ const size_t kRightAxisIndices[] = {
+ AXIS_INDEX_RIGHT_STICK_X, // Axes assume the Joy-Con is held vertically
+ AXIS_INDEX_RIGHT_STICK_Y, // or is attached to a grip.
+ };
+ const size_t kRightAxisIndicesSize = base::size(kRightAxisIndices);
+
+ for (size_t i = 0; i < kRightButtonIndicesSize; ++i)
+ UpdateButtonForRightSide(pad_, pad, kRightButtonIndices[i], horizontal);
+ for (size_t i = 0; i < kRightAxisIndicesSize; ++i)
+ UpdateAxisForRightSide(pad_, pad, kRightAxisIndices[i], horizontal);
+ pad.timestamp = std::max(pad.timestamp, pad_.timestamp);
+ if (!pad_.connected)
+ pad.connected = false;
}
void NintendoController::Connect(mojom::HidManager::ConnectCallback callback) {
@@ -163,14 +1126,553 @@
void NintendoController::OnConnect(mojom::HidConnectionPtr connection) {
if (connection) {
connection_ = std::move(connection);
- // TODO(mattreynolds): Start listening for input reports.
+ ReadInputReport();
StartInitSequence();
}
}
void NintendoController::StartInitSequence() {
- // TODO(mattreynolds): Implement initialization sequence.
- NOTIMPLEMENTED();
+ if (is_composite_) {
+ if (composite_left_ && composite_left_->IsOpen() && composite_right_ &&
+ composite_right_->IsOpen()) {
+ DCHECK_EQ(composite_left_->GetGamepadHand(), GamepadHand::kLeft);
+ DCHECK_EQ(composite_right_->GetGamepadHand(), GamepadHand::kRight);
+ FinishInitSequence();
+ } else {
+ FailInitSequence();
+ }
+ return;
+ }
+
+ switch (bus_type_) {
+ case GAMEPAD_BUS_USB:
+ DCHECK(timeout_callback_.IsCancelled());
+ MakeInitSequenceRequests(kPendingMacAddress);
+ break;
+ case GAMEPAD_BUS_BLUETOOTH:
+ DCHECK(timeout_callback_.IsCancelled());
+ MakeInitSequenceRequests(kPendingSetPlayerLights);
+ break;
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void NintendoController::FinishInitSequence() {
+ state_ = kInitialized;
+ UpdatePadConnected();
+ if (device_ready_closure_)
+ std::move(device_ready_closure_).Run();
+}
+
+void NintendoController::FailInitSequence() {
+ state_ = kUninitialized;
+ UpdatePadConnected();
+}
+
+void NintendoController::HandleInputReport(
+ uint8_t report_id,
+ const std::vector<uint8_t>& report_bytes) {
+ // Register to receive the next input report.
+ ReadInputReport();
+
+ // Listen for reports related to the initialization sequence or gamepad state.
+ // Other reports are ignored.
+ if (bus_type_ == GAMEPAD_BUS_USB && report_id == kUsbReportIdInput81)
+ HandleUsbInputReport81(report_bytes);
+ else if (report_id == kReportIdInput21)
+ HandleInputReport21(report_bytes);
+ else if (report_id == kReportIdInput30)
+ HandleInputReport30(report_bytes);
+
+ // Check whether the input report should cause us to transition to the next
+ // initialization step.
+ if (state_ != kInitialized && state_ != kUninitialized)
+ ContinueInitSequence(report_id, report_bytes);
+}
+
+void NintendoController::HandleUsbInputReport81(
+ const std::vector<uint8_t>& report_bytes) {
+ const auto* ack_report =
+ reinterpret_cast<const UsbInputReport81*>(report_bytes.data());
+ switch (ack_report->subtype) {
+ case kSubTypeRequestMac: {
+ const auto* mac_report =
+ reinterpret_cast<const MacAddressReport*>(report_bytes.data());
+ mac_address_ = UnpackSwitchMacAddress(mac_report->mac_data);
+ if (usb_device_type_ != mac_report->device_type) {
+ usb_device_type_ = mac_report->device_type;
+ switch (usb_device_type_) {
+ case kUsbDeviceTypeChargingGripNoDevice:
+ UpdatePadConnected();
+ // If this was received from an initialized gamepad it means one of
+ // the Joy-Cons was disconnected from the charging grip. The HID
+ // device does not disconnect; de-initialize the device so the
+ // composite device will be hidden.
+ if (state_ == kInitialized)
+ FailInitSequence();
+ break;
+ case kUsbDeviceTypeChargingGripJoyConL:
+ case kUsbDeviceTypeChargingGripJoyConR:
+ UpdatePadConnected();
+ // A Joy-Con was connected to a de-initialized device. Restart the
+ // initialization sequence.
+ if (state_ == kUninitialized)
+ StartInitSequence();
+ break;
+ default:
+ break;
+ }
+ }
+ break;
+ }
+ default:
+ break;
+ }
+}
+
+void NintendoController::HandleInputReport21(
+ const std::vector<uint8_t>& report_bytes) {
+ const auto* spi_report =
+ reinterpret_cast<const SpiReadReport*>(report_bytes.data());
+ if (UpdateGamepadFromControllerData(spi_report->controller_data, cal_data_,
+ pad_)) {
+ pad_.timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
+ }
+ // The input report includes the parameters for the SPI read request along
+ // with the data that was read. Use the read address to determine how to
+ // unpack the data.
+ if (spi_report->subcommand == kSubCommandReadSpi) {
+ uint16_t address = (spi_report->addrh << 8) | spi_report->addrl;
+ switch (address) {
+ case kSpiImuCalibrationAddress:
+ UnpackSwitchImuCalibration(spi_report->spi_data, cal_data_);
+ break;
+ case kSpiImuHorizontalOffsetsAddress:
+ UnpackSwitchImuHorizontalOffsets(spi_report->spi_data, cal_data_);
+ break;
+ case kSpiAnalogStickCalibrationAddress:
+ UnpackSwitchAnalogStickCalibration(spi_report->spi_data, cal_data_);
+ break;
+ case kSpiAnalogStickParametersAddress:
+ UnpackSwitchAnalogStickParameters(spi_report->spi_data, cal_data_);
+ break;
+ default:
+ break;
+ }
+ }
+}
+
+void NintendoController::HandleInputReport30(
+ const std::vector<uint8_t>& report_bytes) {
+ const auto* controller_report =
+ reinterpret_cast<const ControllerDataReport*>(report_bytes.data());
+ // Each input report contains three frames of IMU data.
+ UnpackSwitchImuData(&controller_report->imu_data[0], &imu_data_[0]);
+ UnpackSwitchImuData(&controller_report->imu_data[12], &imu_data_[1]);
+ UnpackSwitchImuData(&controller_report->imu_data[24], &imu_data_[2]);
+ if (UpdateGamepadFromControllerData(controller_report->controller_data,
+ cal_data_, pad_)) {
+ pad_.timestamp = GamepadDataFetcher::CurrentTimeInMicroseconds();
+ }
+}
+
+void NintendoController::ContinueInitSequence(
+ uint8_t report_id,
+ const std::vector<uint8_t>& report_bytes) {
+ const auto* ack_report =
+ reinterpret_cast<const UsbInputReport81*>(report_bytes.data());
+ const auto* spi_report =
+ reinterpret_cast<const SpiReadReport*>(report_bytes.data());
+ const uint8_t ack_subtype =
+ (report_id == kUsbReportIdInput81) ? ack_report->subtype : 0;
+ const uint8_t spi_subcommand =
+ (report_id == kReportIdInput21) ? spi_report->subcommand : 0;
+ const bool is_spi_read =
+ (report_id == kReportIdInput21 && spi_subcommand == kSubCommandReadSpi);
+ const uint16_t spi_read_address =
+ is_spi_read ? ((spi_report->addrh << 8) | spi_report->addrl) : 0;
+ const uint16_t spi_read_length = is_spi_read ? spi_report->length : 0;
+
+ switch (state_) {
+ case kPendingMacAddress:
+ if (ack_subtype == kSubTypeRequestMac) {
+ CancelTimeout();
+ if (mac_address_)
+ MakeInitSequenceRequests(kPendingHandshake1);
+ else
+ FailInitSequence();
+ }
+ break;
+ case kPendingHandshake1:
+ if (ack_subtype == kSubTypeHandshake) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingBaudRate);
+ }
+ break;
+ case kPendingBaudRate:
+ if (ack_subtype == kSubTypeBaudRate) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingHandshake2);
+ }
+ break;
+ case kPendingHandshake2:
+ if (ack_subtype == kSubTypeHandshake) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingDisableUsbTimeout);
+ }
+ break;
+ case kPendingDisableUsbTimeout:
+ if (spi_subcommand == kSubCommand33) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingSetPlayerLights);
+ }
+ break;
+ case kPendingSetPlayerLights:
+ if (spi_subcommand == kSubCommandSetPlayerLights) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingEnableImu);
+ }
+ break;
+ case kPendingEnableImu:
+ if (spi_subcommand == kSubCommandEnableImu) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingSetImuSensitivity);
+ }
+ break;
+ case kPendingSetImuSensitivity:
+ if (spi_subcommand == kSubCommandSetImuSensitivity) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingReadImuCalibration);
+ }
+ break;
+ case kPendingReadImuCalibration:
+ if (spi_read_address == kSpiImuCalibrationAddress &&
+ spi_read_length == kSpiImuCalibrationSize) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingReadHorizontalOffsets);
+ }
+ break;
+ case kPendingReadHorizontalOffsets:
+ if (spi_read_address == kSpiImuHorizontalOffsetsAddress &&
+ spi_read_length == kSpiImuHorizontalOffsetsSize) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingReadAnalogStickCalibration);
+ }
+ break;
+ case kPendingReadAnalogStickCalibration:
+ if (spi_read_address == kSpiAnalogStickCalibrationAddress &&
+ spi_read_length == kSpiAnalogStickCalibrationSize) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingReadAnalogStickParameters);
+ }
+ break;
+ case kPendingReadAnalogStickParameters:
+ if (spi_read_address == kSpiAnalogStickParametersAddress &&
+ spi_read_length == kSpiAnalogStickParametersSize) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingEnableVibration);
+ }
+ break;
+ case kPendingEnableVibration:
+ if (spi_subcommand == kSubCommandEnableVibration) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingSetHomeLight);
+ }
+ break;
+ case kPendingSetHomeLight:
+ if (spi_subcommand == kSubCommandSetHomeLight) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingSetInputReportMode);
+ }
+ break;
+ case kPendingSetInputReportMode:
+ if (spi_subcommand == kSubCommandSetInputReportMode) {
+ CancelTimeout();
+ MakeInitSequenceRequests(kPendingControllerData);
+ }
+ break;
+ case kPendingControllerData:
+ if (report_id == kReportIdInput30) {
+ CancelTimeout();
+ FinishInitSequence();
+ }
+ break;
+ case kInitialized:
+ case kUninitialized:
+ NOTREACHED();
+ break;
+ default:
+ break;
+ }
+}
+
+void NintendoController::MakeInitSequenceRequests(InitializationState state) {
+ DCHECK(timeout_callback_.IsCancelled());
+ state_ = state;
+ switch (state_) {
+ case kPendingMacAddress:
+ RequestMacAddress();
+ break;
+ case kPendingHandshake1:
+ case kPendingHandshake2:
+ RequestHandshake();
+ break;
+ case kPendingBaudRate:
+ RequestBaudRate();
+ break;
+ case kPendingDisableUsbTimeout:
+ RequestEnableUsbTimeout(false);
+ break;
+ case kPendingSetPlayerLights:
+ RequestSetPlayerLights(kPlayerLightPattern1); // Player 1 indicator on.
+ break;
+ case kPendingEnableImu:
+ RequestEnableImu(false); // IMU disabled.
+ break;
+ case kPendingSetImuSensitivity:
+ RequestSetImuSensitivity(
+ kGyroSensitivity2000Dps, kAccelerometerSensitivity8G,
+ kGyroPerformance208Hz, kAccelerometerFilterBandwidth100Hz);
+ break;
+ case kPendingReadImuCalibration:
+ RequestImuCalibration();
+ break;
+ case kPendingReadHorizontalOffsets:
+ RequestHorizontalOffsets();
+ break;
+ case kPendingReadAnalogStickCalibration:
+ RequestAnalogCalibration();
+ break;
+ case kPendingReadAnalogStickParameters:
+ RequestAnalogParameters();
+ break;
+ case kPendingEnableVibration:
+ RequestEnableVibration(true);
+ break;
+ case kPendingSetHomeLight:
+ RequestSetHomeLightIntensity(1.0); // 100% intensity.
+ break;
+ case kPendingSetInputReportMode:
+ RequestSetInputReportMode(0x30); // Standard full mode reported at 60Hz.
+ break;
+ case kPendingControllerData:
+ ArmTimeout();
+ break;
+ case kInitialized:
+ case kUninitialized:
+ default:
+ NOTREACHED();
+ break;
+ }
+}
+
+void NintendoController::SubCommand(uint8_t sub_command,
+ const std::vector<uint8_t>& bytes) {
+ std::vector<uint8_t> report_bytes(output_report_size_bytes_ - 1);
+ report_bytes[0] = uint8_t{counter_++ & 0xff};
+ report_bytes[1] = 0x00;
+ report_bytes[2] = 0x01;
+ report_bytes[3] = 0x40;
+ report_bytes[4] = 0x40;
+ report_bytes[5] = 0x00;
+ report_bytes[6] = 0x01;
+ report_bytes[7] = 0x40;
+ report_bytes[8] = 0x40;
+ report_bytes[9] = sub_command;
+ DCHECK_LT(bytes.size() + kSubCommandDataOffset, output_report_size_bytes_);
+ std::copy(bytes.begin(), bytes.end(),
+ &report_bytes[kSubCommandDataOffset - 1]);
+ WriteOutputReport(kReportIdOutput01, report_bytes, true);
+}
+
+void NintendoController::RequestMacAddress() {
+ std::vector<uint8_t> report_bytes(output_report_size_bytes_ - 1);
+ report_bytes[0] = kSubTypeRequestMac;
+ WriteOutputReport(kUsbReportIdOutput80, report_bytes, true);
+}
+
+void NintendoController::RequestHandshake() {
+ std::vector<uint8_t> report_bytes(output_report_size_bytes_ - 1);
+ report_bytes[0] = kSubTypeHandshake;
+ WriteOutputReport(kUsbReportIdOutput80, report_bytes, true);
+}
+
+void NintendoController::RequestBaudRate() {
+ std::vector<uint8_t> report_bytes(output_report_size_bytes_ - 1);
+ report_bytes[0] = kSubTypeBaudRate;
+ WriteOutputReport(kUsbReportIdOutput80, report_bytes, true);
+}
+
+void NintendoController::RequestSubCommand33() {
+ // Unrecognized commands do nothing, but still generate a reply.
+ SubCommand(kSubCommand33, {});
+}
+
+void NintendoController::RequestVibration(double left_frequency,
+ double left_magnitude,
+ double right_frequency,
+ double right_magnitude) {
+ uint16_t lhf;
+ uint8_t llf;
+ uint8_t lhfa;
+ uint16_t llfa;
+ uint16_t rhf;
+ uint8_t rlf;
+ uint8_t rhfa;
+ uint16_t rlfa;
+ FrequencyToHex(left_frequency, left_magnitude, &lhf, &llf, &lhfa, &llfa);
+ FrequencyToHex(right_frequency, right_magnitude, &rhf, &rlf, &rhfa, &rlfa);
+ std::vector<uint8_t> report_bytes(output_report_size_bytes_ - 1);
+ uint8_t counter = uint8_t{counter_++ & 0x0f};
+ report_bytes[0] = counter;
+ report_bytes[1] = lhf & 0xff;
+ report_bytes[2] = lhfa + ((lhf >> 8) & 0xff);
+ report_bytes[3] = llf + ((llfa >> 8) & 0xff);
+ report_bytes[4] = llfa & 0xff;
+ report_bytes[5] = rhf & 0xff;
+ report_bytes[6] = rhfa + ((rhf >> 8) & 0xff);
+ report_bytes[7] = rlf + ((rlfa >> 8) & 0xff);
+ report_bytes[8] = rlfa & 0xff;
+ WriteOutputReport(kReportIdOutput10, report_bytes, false);
+}
+
+void NintendoController::RequestEnableUsbTimeout(bool enable) {
+ // By default, Switch Pro will revert to Bluetooth mode if it does not
+ // receive any USB HID commands within a timeout window. Disabling the
+ // timeout keeps the device in USB mode.
+ std::vector<uint8_t> report_bytes(output_report_size_bytes_ - 1);
+ report_bytes[0] =
+ enable ? kSubTypeEnableUsbTimeout : kSubTypeDisableUsbTimeout;
+ // This report may not be acked due to a software bug on the device.
+ WriteOutputReport(kUsbReportIdOutput80, report_bytes, false);
+ // Send an unused subcommand (0x33) which is acked.
+ RequestSubCommand33();
+}
+
+void NintendoController::RequestEnableImu(bool enable) {
+ SubCommand(kSubCommandEnableImu, {enable ? 0x01 : 0x00});
+}
+
+void NintendoController::RequestEnableVibration(bool enable) {
+ SubCommand(kSubCommandEnableVibration, {enable ? 0x01 : 0x00});
+}
+
+void NintendoController::RequestSetPlayerLights(uint8_t light_pattern) {
+ SubCommand(kSubCommandSetPlayerLights, {light_pattern});
+}
+
+void NintendoController::RequestSetHomeLight(
+ uint8_t minicycle_count,
+ uint8_t minicycle_duration,
+ uint8_t start_intensity,
+ uint8_t cycle_count,
+ const std::vector<uint8_t>& minicycle_data) {
+ DCHECK_LE(minicycle_count, 0xf);
+ DCHECK_LE(minicycle_duration, 0xf);
+ DCHECK_LE(start_intensity, 0xf);
+ DCHECK_LE(cycle_count, 0xf);
+ if ((cycle_count > 0 && minicycle_count == 1) || minicycle_duration == 0)
+ minicycle_count = 0;
+ std::vector<uint8_t> bytes = {(minicycle_count << 4) | minicycle_duration,
+ (start_intensity << 4) | cycle_count};
+ bytes.insert(bytes.end(), minicycle_data.begin(), minicycle_data.end());
+ SubCommand(kSubCommandSetHomeLight, bytes);
+}
+
+void NintendoController::RequestSetHomeLightIntensity(double intensity) {
+ // Clamp |intensity| to [0,1].
+ intensity = std::max(0.0, std::min(1.0, intensity));
+ uint8_t led_intensity = std::round(intensity * 0x0f);
+ // Each pair of bytes in the minicycle data describes two minicyles.
+ // The first byte holds two 4-bit values encoding minicycle intensities.
+ // The second byte holds two 4-bit multipliers for the duration of each
+ // transition.
+ //
+ // This command encodes one minicycle that transitions to 100% intensity after
+ // 1x minicycle duration. Because |minicycle_count| and |cycle_count| are
+ // both zero, the device will transition to the 1st minicycle and then stay at
+ // |led_intensity|.
+ RequestSetHomeLight(0, 1, led_intensity, 0, {led_intensity << 4, 0x00});
+}
+
+void NintendoController::RequestSetImuSensitivity(
+ uint8_t gyro_sensitivity,
+ uint8_t accelerometer_sensitivity,
+ uint8_t gyro_performance_rate,
+ uint8_t accelerometer_filter_bandwidth) {
+ SubCommand(kSubCommandSetImuSensitivity,
+ {gyro_sensitivity, accelerometer_sensitivity,
+ gyro_performance_rate, accelerometer_filter_bandwidth});
+}
+
+void NintendoController::RequestSetInputReportMode(uint8_t mode) {
+ SubCommand(kSubCommandSetInputReportMode, {mode});
+}
+
+void NintendoController::ReadSpi(uint16_t address, size_t length) {
+ DCHECK_LE(length + kSpiDataOffset, output_report_size_bytes_);
+ length = std::min(length, output_report_size_bytes_ - kSpiDataOffset);
+ uint8_t address_high = (address >> 8) & 0xff;
+ uint8_t address_low = address & 0xff;
+ SubCommand(kSubCommandReadSpi,
+ {address_low, address_high, 0x00, 0x00, uint8_t{length}});
+}
+
+void NintendoController::RequestImuCalibration() {
+ ReadSpi(kSpiImuCalibrationAddress, kSpiImuCalibrationSize);
+}
+
+void NintendoController::RequestHorizontalOffsets() {
+ ReadSpi(kSpiImuHorizontalOffsetsAddress, kSpiImuHorizontalOffsetsSize);
+}
+
+void NintendoController::RequestAnalogCalibration() {
+ ReadSpi(kSpiAnalogStickCalibrationAddress, kSpiAnalogStickCalibrationSize);
+}
+
+void NintendoController::RequestAnalogParameters() {
+ ReadSpi(kSpiAnalogStickParametersAddress, kSpiAnalogStickParametersSize);
+}
+
+void NintendoController::ReadInputReport() {
+ DCHECK(connection_);
+ connection_->Read(base::BindOnce(&NintendoController::OnReadInputReport,
+ weak_factory_.GetWeakPtr()));
+}
+
+void NintendoController::OnReadInputReport(
+ bool success,
+ uint8_t report_id,
+ const base::Optional<std::vector<uint8_t>>& report_bytes) {
+ if (success) {
+ DCHECK(report_bytes);
+ HandleInputReport(report_id, *report_bytes);
+ } else {
+ CancelTimeout();
+ FailInitSequence();
+ }
+}
+
+void NintendoController::WriteOutputReport(
+ uint8_t report_id,
+ const std::vector<uint8_t>& report_bytes,
+ bool expect_reply) {
+ DCHECK(connection_);
+ DCHECK(timeout_callback_.IsCancelled());
+ connection_->Write(report_id, report_bytes,
+ base::BindOnce(&NintendoController::OnWriteOutputReport,
+ weak_factory_.GetWeakPtr()));
+ if (expect_reply)
+ ArmTimeout();
+}
+
+void NintendoController::OnWriteOutputReport(bool success) {
+ if (!success) {
+ CancelTimeout();
+ FailInitSequence();
+ }
}
void NintendoController::DoShutdown() {
@@ -186,8 +1688,43 @@
void NintendoController::SetVibration(double strong_magnitude,
double weak_magnitude) {
- // TODO(mattreynolds): Set vibration.
- NOTIMPLEMENTED();
+ if (is_composite_) {
+ // Split the vibration effect between the left and right subdevices.
+ composite_left_->SetVibration(strong_magnitude, 0);
+ composite_right_->SetVibration(0, weak_magnitude);
+ } else {
+ RequestVibration(kVibrationFrequencyStrongRumble,
+ kVibrationAmplitudeStrongRumbleMax * strong_magnitude,
+ kVibrationFrequencyWeakRumble,
+ kVibrationAmplitudeWeakRumbleMax * weak_magnitude);
+ }
+}
+
+double NintendoController::GetMaxEffectDurationMillis() {
+ return kMaxVibrationEffectDurationMillis;
+}
+
+void NintendoController::ArmTimeout() {
+ DCHECK(timeout_callback_.IsCancelled());
+ timeout_callback_.Reset(base::BindOnce(&NintendoController::OnTimeout,
+ weak_factory_.GetWeakPtr()));
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE, timeout_callback_.callback(), kTimeoutDuration);
+}
+
+void NintendoController::CancelTimeout() {
+ timeout_callback_.Cancel();
+ retry_count_ = 0;
+}
+
+void NintendoController::OnTimeout() {
+ ++retry_count_;
+ if (retry_count_ <= kMaxRetryCount)
+ MakeInitSequenceRequests(state_);
+ else {
+ retry_count_ = 0;
+ StartInitSequence();
+ }
}
} // namespace device
diff --git a/device/gamepad/nintendo_controller.h b/device/gamepad/nintendo_controller.h
index 356187d..cd3f033 100644
--- a/device/gamepad/nintendo_controller.h
+++ b/device/gamepad/nintendo_controller.h
@@ -5,9 +5,15 @@
#ifndef DEVICE_GAMEPAD_NINTENDO_CONTROLLER_H_
#define DEVICE_GAMEPAD_NINTENDO_CONTROLLER_H_
-#include "device/gamepad/abstract_haptic_gamepad.h"
+#include <memory>
+#include <vector>
+#include "base/cancelable_callback.h"
+#include "base/optional.h"
+#include "device/gamepad/abstract_haptic_gamepad.h"
+#include "device/gamepad/gamepad_id_list.h"
#include "device/gamepad/gamepad_standard_mappings.h"
+#include "device/gamepad/public/cpp/gamepad.h"
#include "services/device/public/mojom/hid.mojom.h"
namespace device {
@@ -52,6 +58,57 @@
// channel of the dual-rumble effect.
class NintendoController : public AbstractHapticGamepad {
public:
+ struct SwitchCalibrationData {
+ SwitchCalibrationData();
+ ~SwitchCalibrationData();
+
+ // Analog stick calibration data.
+ uint16_t lx_center = 0;
+ uint16_t lx_min = 0;
+ uint16_t lx_max = 0;
+ uint16_t ly_center = 0;
+ uint16_t ly_min = 0;
+ uint16_t ly_max = 0;
+ uint16_t rx_center = 0;
+ uint16_t rx_min = 0;
+ uint16_t rx_max = 0;
+ uint16_t ry_center = 0;
+ uint16_t ry_min = 0;
+ uint16_t ry_max = 0;
+ uint16_t dead_zone = 0;
+ uint16_t range_ratio = 0;
+
+ // IMU calibration data.
+ uint16_t accelerometer_origin_x = 0;
+ uint16_t accelerometer_origin_y = 0;
+ uint16_t accelerometer_origin_z = 0;
+ uint16_t accelerometer_sensitivity_x = 0;
+ uint16_t accelerometer_sensitivity_y = 0;
+ uint16_t accelerometer_sensitivity_z = 0;
+ uint16_t gyro_origin_x = 0;
+ uint16_t gyro_origin_y = 0;
+ uint16_t gyro_origin_z = 0;
+ uint16_t gyro_sensitivity_x = 0;
+ uint16_t gyro_sensitivity_y = 0;
+ uint16_t gyro_sensitivity_z = 0;
+ uint16_t horizontal_offset_x = 0;
+ uint16_t horizontal_offset_y = 0;
+ uint16_t horizontal_offset_z = 0;
+ };
+
+ // One frame of accerometer and gyroscope data.
+ struct SwitchImuData {
+ SwitchImuData();
+ ~SwitchImuData();
+
+ uint16_t accelerometer_x;
+ uint16_t accelerometer_y;
+ uint16_t accelerometer_z;
+ uint16_t gyro_x;
+ uint16_t gyro_y;
+ uint16_t gyro_z;
+ };
+
~NintendoController() override;
// Create a NintendoController for a newly-connected HID device.
@@ -81,7 +138,7 @@
// Return true if the device has completed initialization and is ready to
// report controller data.
- bool IsOpen() const { return false; }
+ bool IsOpen() const { return is_composite_ || connection_.is_bound(); }
// Return true if the device is ready to be assigned a gamepad slot.
bool IsUsable() const;
@@ -107,6 +164,14 @@
// Update the button and axis state in |pad|.
void UpdateGamepadState(Gamepad& pad) const;
+ // Update the button and axis state in |pad| for a Joy-Con L or for the left
+ // side of a Pro controller. If |horizontal| is true, also remap buttons and
+ // axes for a horizontal orientation.
+ void UpdateLeftGamepadState(Gamepad& pad, bool horizontal) const;
+ // Update the button and axis state in |pad| for a Joy-Con R or for the right
+ // side of a Pro controller. If |horizontal| is true, also remap buttons and
+ // axes for a horizontal orientation.
+ void UpdateRightGamepadState(Gamepad& pad, bool horizontal) const;
// Return the handedness of the device, or GamepadHand::kNone if the device
// is not intended to be used in a specific hand.
@@ -115,6 +180,7 @@
// AbstractHapticGamepad implementation.
void DoShutdown() override;
void SetVibration(double strong_magnitude, double weak_magnitude) override;
+ double GetMaxEffectDurationMillis() override;
NintendoController(int source_id,
mojom::HidDeviceInfoPtr device_info,
@@ -125,6 +191,47 @@
mojom::HidManager* hid_manager);
private:
+ enum InitializationState {
+ // Switch Pro requires initialization to configure the device for USB mode
+ // and to read calibration data.
+ kUninitialized = 0,
+ // Fetch the MAC address. This allows us to identify when the device is
+ // double-connected through USB and Bluetooth.
+ kPendingMacAddress,
+ // Increase the baud rate to improve latency. This requires a handshake
+ // before and after the change.
+ kPendingHandshake1,
+ kPendingBaudRate,
+ kPendingHandshake2,
+ // Disable the USB timeout. This subcommand is not acked, so also send an
+ // unused subcommand (0x33) which is acked.
+ kPendingDisableUsbTimeout,
+ // Set the player lights to the default (player 1).
+ kPendingSetPlayerLights,
+ // Disable the accelerometer and gyro.
+ kPendingEnableImu,
+ // Configure accelerometer and gyro sensitivity.
+ kPendingSetImuSensitivity,
+ // Read the calibration settings for the accelerometer and gyro.
+ kPendingReadImuCalibration,
+ // Read the dead zone and range ratio for the analog sticks.
+ kPendingReadAnalogStickParameters,
+ // Read the calibration settings for the horizontal orientation.
+ kPendingReadHorizontalOffsets,
+ // Read the calibration settings for the analog sticks.
+ kPendingReadAnalogStickCalibration,
+ // Enable vibration.
+ kPendingEnableVibration,
+ // Turn on the Home light.
+ kPendingSetHomeLight,
+ // Set standard full mode (60 Hz).
+ kPendingSetInputReportMode,
+ // Wait for controller data to be received.
+ kPendingControllerData,
+ // Fully initialized.
+ kInitialized,
+ };
+
// Initiate a connection request to the HID device.
void Connect(mojom::HidManager::ConnectCallback callback);
@@ -135,12 +242,113 @@
// controller data.
void StartInitSequence();
+ // Transition to |state| and makes the request(s) associated with the state.
+ // May be called repeatedly to retry the current initialization step.
+ void MakeInitSequenceRequests(InitializationState state);
+
+ // Mark the device as initialized.
+ void FinishInitSequence();
+
+ // Mark the device as uninitialized.
+ void FailInitSequence();
+
+ // Handle an input report sent by the device. The first byte of the report
+ // (the report ID) has been extracted to |report_id| and the remaining bytes
+ // are in |report_bytes|.
+ void HandleInputReport(uint8_t report_id,
+ const std::vector<uint8_t>& report_bytes);
+
+ // Handle a USB input report with report ID 0x81. These reports are used
+ // during device initialization.
+ void HandleUsbInputReport81(const std::vector<uint8_t>& report_bytes);
+
+ // Handle a USB or Bluetooth input report with ID 0x21. These reports carry
+ // controller data and subcommand responses.
+ void HandleInputReport21(const std::vector<uint8_t>& report_bytes);
+
+ // Handle a USB or Bluetooth input report with ID 0x30. These reports carry
+ // controller data and IMU data.
+ void HandleInputReport30(const std::vector<uint8_t>& report_bytes);
+
+ // Check the result of a received input report and decide whether to
+ // transition to the next step in the initialization sequence.
+ void ContinueInitSequence(uint8_t report_id,
+ const std::vector<uint8_t>& report_bytes);
+
+ // Update |pad_.connected| based on the current device state.
+ void UpdatePadConnected();
+
+ // Register to receive the next input report from the underlying HID device.
+ void ReadInputReport();
+
+ // Callback to be called when an input report is received, or when the read
+ // has failed.
+ void OnReadInputReport(
+ bool success,
+ uint8_t report_id,
+ const base::Optional<std::vector<uint8_t>>& report_bytes);
+
+ // Request to send an output report to the underlying HID device. If
+ // |expect_reply| is true, the timeout is armed.
+ void WriteOutputReport(uint8_t report_id,
+ const std::vector<uint8_t>& report_bytes,
+ bool expect_reply);
+
+ // Callback to be called when an output report request is complete or has
+ // failed.
+ void OnWriteOutputReport(bool success);
+
+ // Output reports sent to the device.
+ void SubCommand(uint8_t sub_command, const std::vector<uint8_t>& bytes);
+ void RequestMacAddress();
+ void RequestHandshake();
+ void RequestBaudRate();
+ void RequestSubCommand33();
+ void RequestVibration(double left_frequency,
+ double left_magnitude,
+ double right_frequency,
+ double right_magnitude);
+ void RequestEnableUsbTimeout(bool enable);
+ void RequestEnableVibration(bool enable);
+ void RequestEnableImu(bool enable);
+ void RequestSetPlayerLights(uint8_t light_pattern);
+ void RequestSetHomeLight(uint8_t minicycle_count,
+ uint8_t minicycle_duration,
+ uint8_t start_intensity,
+ uint8_t cycle_count,
+ const std::vector<uint8_t>& minicycle_data);
+ void RequestSetHomeLightIntensity(double intensity);
+ void RequestSetImuSensitivity(uint8_t gyro_sensitivity,
+ uint8_t accelerometer_sensitivity,
+ uint8_t gyro_performance_rate,
+ uint8_t accelerometer_filter_bandwidth);
+ void RequestSetInputReportMode(uint8_t mode);
+ void ReadSpi(uint16_t address, size_t length);
+ void RequestImuCalibration();
+ void RequestHorizontalOffsets();
+ void RequestAnalogCalibration();
+ void RequestAnalogParameters();
+
+ // Schedule a callback to retry a step during the initialization sequence.
+ void ArmTimeout();
+
+ // Cancel the current timeout, if there is one.
+ void CancelTimeout();
+
+ // Timeout expiration callback.
+ void OnTimeout();
+
// An ID value to identify this device among other devices enumerated by the
// data fetcher.
const int source_id_;
- // The bus type for the underlying HID device.
- GamepadBusType bus_type_ = GAMEPAD_BUS_UNKNOWN;
+ // The current step of the initialization sequence, or kInitialized if the
+ // device is already initialized. Set to kUninitialized if the device is
+ // in a temporary de-initialized state but is still connected.
+ InitializationState state_ = kUninitialized;
+
+ // The number of times the current initialization step has been retried.
+ size_t retry_count_ = 0;
// A composite device contains up to two Joy-Cons as sub-devices.
bool is_composite_ = false;
@@ -149,12 +357,40 @@
std::unique_ptr<NintendoController> composite_left_;
std::unique_ptr<NintendoController> composite_right_;
+ // Global counter.
+ uint8_t counter_ = 0;
+
+ // The Bluetooth MAC address of the device.
+ uint64_t mac_address_ = 0;
+
+ // The bus type for the underlying HID device.
+ GamepadBusType bus_type_ = GAMEPAD_BUS_UNKNOWN;
+
+ // The maximum size of an output report for the underlying HID device.
+ size_t output_report_size_bytes_ = 0;
+
+ // 8-bit value representing the device type, as reported by the device when
+ // connected over USB.
+ uint8_t usb_device_type_ = 0;
+
+ // Calibration data read from the device.
+ SwitchCalibrationData cal_data_;
+
+ // The last collection of IMU data. The device reports three frames of data in
+ // each update.
+ SwitchImuData imu_data_[3];
+
// The most recent gamepad state.
Gamepad pad_;
+ // A callback to be called once the initialization timeout has expired.
+ base::CancelableOnceClosure timeout_callback_;
+
// Information about the underlying HID device.
mojom::HidDeviceInfoPtr device_info_;
+ GamepadId gamepad_id_;
+
// HID service manager.
mojom::HidManager* const hid_manager_;
diff --git a/device/gamepad/nintendo_data_fetcher.cc b/device/gamepad/nintendo_data_fetcher.cc
index 2e90cb2..0477a5b 100644
--- a/device/gamepad/nintendo_data_fetcher.cc
+++ b/device/gamepad/nintendo_data_fetcher.cc
@@ -7,6 +7,7 @@
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "device/gamepad/gamepad_service.h"
+#include "device/gamepad/gamepad_uma.h"
#include "mojo/public/cpp/bindings/interface_request.h"
#include "services/device/public/mojom/constants.mojom.h"
#include "services/service_manager/public/cpp/connector.h"
@@ -16,7 +17,12 @@
NintendoDataFetcher::NintendoDataFetcher()
: binding_(this), weak_factory_(this) {}
-NintendoDataFetcher::~NintendoDataFetcher() = default;
+NintendoDataFetcher::~NintendoDataFetcher() {
+ for (auto& entry : controllers_) {
+ auto& device = *entry.second;
+ device.Shutdown();
+ }
+}
GamepadSource NintendoDataFetcher::source() {
return Factory::static_source();
@@ -44,10 +50,9 @@
void NintendoDataFetcher::OnDeviceReady(int source_id) {
auto find_it = controllers_.find(source_id);
- if (find_it == controllers_.end()) {
- NOTREACHED();
+ if (find_it == controllers_.end())
return;
- }
+
const auto* ready_device_ptr = find_it->second.get();
DCHECK(ready_device_ptr);
if (ready_device_ptr->IsComposite())
@@ -97,19 +102,17 @@
bool NintendoDataFetcher::AddDevice(mojom::HidDeviceInfoPtr device_info) {
DCHECK(hid_manager_);
- if (NintendoController::IsNintendoController(device_info->vendor_id,
- device_info->product_id)) {
- int source_id = next_source_id_++;
- auto emplace_result = controllers_.emplace(
- source_id, NintendoController::Create(source_id, std::move(device_info),
- hid_manager_.get()));
- if (emplace_result.second) {
- auto& new_device = emplace_result.first->second;
- DCHECK(new_device);
- new_device->Open(base::BindOnce(&NintendoDataFetcher::OnDeviceReady,
- weak_factory_.GetWeakPtr(), source_id));
- return true;
- }
+ RecordConnectedGamepad(device_info->vendor_id, device_info->product_id);
+ int source_id = next_source_id_++;
+ auto emplace_result = controllers_.emplace(
+ source_id, NintendoController::Create(source_id, std::move(device_info),
+ hid_manager_.get()));
+ if (emplace_result.second) {
+ auto& new_device = emplace_result.first->second;
+ DCHECK(new_device);
+ new_device->Open(base::BindOnce(&NintendoDataFetcher::OnDeviceReady,
+ weak_factory_.GetWeakPtr(), source_id));
+ return true;
}
return false;
}
@@ -162,6 +165,15 @@
}
}
}
+
+ // Set the PadState source back to the default to signal that the slot
+ // occupied by the associated device is no longer in use.
+ if (associated_device) {
+ PadState* state = GetPadState(associated_device->GetSourceId());
+ if (state)
+ state->source = GAMEPAD_SOURCE_NONE;
+ }
+
return associated_device;
}
diff --git a/device/gamepad/nintendo_data_fetcher.h b/device/gamepad/nintendo_data_fetcher.h
index 5a6602a..32f1c5f 100644
--- a/device/gamepad/nintendo_data_fetcher.h
+++ b/device/gamepad/nintendo_data_fetcher.h
@@ -7,6 +7,7 @@
#include <memory>
#include <string>
+#include <unordered_map>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
@@ -17,7 +18,6 @@
#include "services/device/public/mojom/hid.mojom.h"
namespace device {
-
// Nintendo controllers are not typical HID gamepads and cannot be easily
// supported through the platform data fetchers. However, when they are HID
// devices we can use the HID backend to enumerate and initialize them.
@@ -45,6 +45,8 @@
public:
using Factory = GamepadDataFetcherFactoryImpl<NintendoDataFetcher,
GAMEPAD_SOURCE_NINTENDO>;
+ using ControllerMap =
+ std::unordered_map<int, std::unique_ptr<NintendoController>>;
NintendoDataFetcher();
~NintendoDataFetcher() override;
@@ -73,6 +75,8 @@
mojom::GamepadHapticsManager::ResetVibrationActuatorCallback callback,
scoped_refptr<base::SequencedTaskRunner> callback_runner) override;
+ const ControllerMap& GetControllersForTesting() const { return controllers_; }
+
private:
// GamepadDataFetcher implementation.
void OnAddedToProvider() override;
@@ -104,7 +108,7 @@
int next_source_id_ = 0;
// A mapping from source ID to connected Nintendo Switch devices.
- std::unordered_map<int, std::unique_ptr<NintendoController>> controllers_;
+ ControllerMap controllers_;
mojom::HidManagerPtr hid_manager_;
mojo::AssociatedBinding<mojom::HidManagerClient> binding_;
diff --git a/device/gamepad/nintendo_data_fetcher_unittest.cc b/device/gamepad/nintendo_data_fetcher_unittest.cc
new file mode 100644
index 0000000..b818a8f
--- /dev/null
+++ b/device/gamepad/nintendo_data_fetcher_unittest.cc
@@ -0,0 +1,129 @@
+// Copyright 2019 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/nintendo_data_fetcher.h"
+
+#include <utility>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted_memory.h"
+#include "base/run_loop.h"
+#include "base/threading/thread.h"
+#include "build/build_config.h"
+#include "device/gamepad/gamepad_service.h"
+#include "services/device/device_service_test_base.h"
+#include "services/device/hid/hid_manager_impl.h"
+#include "services/device/hid/mock_hid_service.h"
+#include "services/device/public/mojom/hid.mojom.h"
+#include "services/service_manager/public/cpp/connector.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace device {
+
+namespace {
+
+#if defined(OS_MACOSX)
+const uint64_t kTestDeviceId = 123;
+#else
+const char* kTestDeviceId = "123";
+#endif
+
+} // namespace
+
+// Main test fixture
+class NintendoDataFetcherTest : public DeviceServiceTestBase {
+ public:
+ NintendoDataFetcherTest() {}
+
+ void SetUp() override {
+ // Set up the fake HID service.
+ auto mock_hid_service = std::make_unique<MockHidService>();
+ mock_hid_service_ = mock_hid_service.get();
+ mock_hid_service_->FirstEnumerationComplete();
+
+ // Transfer the ownership of the |mock_hid_service| to HidManagerImpl.
+ // It is safe to use |mock_hid_service_| in this test.
+ HidManagerImpl::SetHidServiceForTesting(std::move(mock_hid_service));
+
+ // Initialize the device service and pass a service connector to the gamepad
+ // service.
+ DeviceServiceTestBase::SetUp();
+ GamepadService::GetInstance()->StartUp(connector()->Clone());
+
+ // Create the data fetcher and polling thread.
+ auto fetcher = std::make_unique<NintendoDataFetcher>();
+ fetcher_ = fetcher.get();
+ auto polling_thread = std::make_unique<base::Thread>("polling thread");
+ polling_thread_ = polling_thread.get();
+ provider_.reset(new GamepadProvider(nullptr, std::move(fetcher),
+ std::move(polling_thread)));
+
+ RunUntilIdle();
+ }
+
+ void TearDown() override {
+ HidManagerImpl::SetHidServiceForTesting(nullptr);
+ GamepadService::SetInstance(nullptr);
+ }
+
+ void RunUntilIdle() {
+ base::RunLoop().RunUntilIdle();
+ polling_thread_->FlushForTesting();
+ }
+
+ MockHidService* mock_hid_service_;
+ std::unique_ptr<GamepadProvider> provider_;
+ NintendoDataFetcher* fetcher_;
+ base::Thread* polling_thread_;
+
+ DISALLOW_COPY_AND_ASSIGN(NintendoDataFetcherTest);
+};
+
+TEST_F(NintendoDataFetcherTest, UnsupportedDeviceIsIgnored) {
+ // Simulate an unsupported, non-Nintendo HID device.
+ auto collection = mojom::HidCollectionInfo::New();
+ collection->usage = mojom::HidUsageAndPage::New(0, 0);
+ scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfo(
+ kTestDeviceId, 0x1234, 0xabcd, "Invalipad", "",
+ mojom::HidBusType::kHIDBusTypeUSB, std::move(collection), 0, 0, 0));
+
+ // Add the device to the mock HID service. The HID service should notify the
+ // data fetcher.
+ mock_hid_service_->AddDevice(device_info);
+ RunUntilIdle();
+
+ // The device should not have been added to the internal device map.
+ EXPECT_TRUE(fetcher_->GetControllersForTesting().empty());
+
+ // Remove the device.
+ mock_hid_service_->RemoveDevice(kTestDeviceId);
+ RunUntilIdle();
+}
+
+TEST_F(NintendoDataFetcherTest, AddAndRemoveSwitchPro) {
+ // Simulate a Switch Pro over USB.
+ auto collection = mojom::HidCollectionInfo::New();
+ collection->usage = mojom::HidUsageAndPage::New(0, 0);
+ scoped_refptr<HidDeviceInfo> device_info(new HidDeviceInfo(
+ kTestDeviceId, 0x057e, 0x2009, "Switch Pro Controller", "",
+ mojom::HidBusType::kHIDBusTypeUSB, std::move(collection), 0, 63, 0));
+
+ // Add the device to the mock HID service. The HID service should notify the
+ // data fetcher.
+ mock_hid_service_->AddDevice(device_info);
+ RunUntilIdle();
+
+ // The fetcher should have added the device to its internal device map.
+ EXPECT_EQ(fetcher_->GetControllersForTesting().size(), 1U);
+
+ // Remove the device.
+ mock_hid_service_->RemoveDevice(kTestDeviceId);
+
+ RunUntilIdle();
+
+ // Check that the device was removed.
+ EXPECT_TRUE(fetcher_->GetControllersForTesting().empty());
+}
+
+} // namespace device
diff --git a/device/gamepad/raw_input_data_fetcher_win.cc b/device/gamepad/raw_input_data_fetcher_win.cc
index caea2ee..39d96f7 100644
--- a/device/gamepad/raw_input_data_fetcher_win.cc
+++ b/device/gamepad/raw_input_data_fetcher_win.cc
@@ -12,6 +12,7 @@
#include "base/trace_event/trace_event.h"
#include "device/gamepad/gamepad_standard_mappings.h"
#include "device/gamepad/gamepad_uma.h"
+#include "device/gamepad/nintendo_controller.h"
namespace device {
@@ -172,6 +173,12 @@
const int version_number = new_device->GetVersionNumber();
const std::wstring product_string = new_device->GetProductString();
+ if (NintendoController::IsNintendoController(vendor_int, product_int)) {
+ // Nintendo devices are handled by the Nintendo data fetcher.
+ new_device->Shutdown();
+ continue;
+ }
+
// Record gamepad metrics before excluding XInput devices. This allows
// us to recognize XInput devices even though the XInput API masks
// the vendor and product IDs.
diff --git a/device/gamepad/switch_pro_controller_base.cc b/device/gamepad/switch_pro_controller_base.cc
deleted file mode 100644
index 2179673..0000000
--- a/device/gamepad/switch_pro_controller_base.cc
+++ /dev/null
@@ -1,321 +0,0 @@
-// 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
diff --git a/device/gamepad/switch_pro_controller_base.h b/device/gamepad/switch_pro_controller_base.h
deleted file mode 100644
index 35290d3..0000000
--- a/device/gamepad/switch_pro_controller_base.h
+++ /dev/null
@@ -1,44 +0,0 @@
-// 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.
-
-#ifndef DEVICE_GAMEPAD_SWITCH_PRO_CONTROLLER_BASE_
-#define DEVICE_GAMEPAD_SWITCH_PRO_CONTROLLER_BASE_
-
-#include "device/gamepad/abstract_haptic_gamepad.h"
-#include "device/gamepad/public/cpp/gamepad.h"
-
-namespace device {
-
-class SwitchProControllerBase : public AbstractHapticGamepad {
- public:
- // Maximum size of a Switch HID report, in bytes.
- static const int kReportSize = 64;
-
- SwitchProControllerBase() = default;
- ~SwitchProControllerBase() override;
-
- static bool IsSwitchPro(uint16_t vendor_id, uint16_t product_id);
-
- void DoShutdown() override;
-
- void ReadUsbPadState(Gamepad* pad);
- void HandleInputReport(void* report, size_t report_length, Gamepad* pad);
-
- void SendConnectionStatusQuery();
- void SendHandshake();
- void SendForceUsbHid(bool enable);
- void SetVibration(double strong_magnitude, double weak_magnitude) override;
-
- virtual size_t ReadInputReport(void* report);
- virtual size_t WriteOutputReport(void* report, size_t report_length);
-
- private:
- uint32_t counter_ = 0;
- bool force_usb_hid_ = false;
- bool sent_handshake_ = false;
-};
-
-} // namespace device
-
-#endif // DEVICE_GAMEPAD_SWITCH_PRO_CONTROLLER_BASE_
diff --git a/device/gamepad/switch_pro_controller_linux.cc b/device/gamepad/switch_pro_controller_linux.cc
deleted file mode 100644
index 2e557e4..0000000
--- a/device/gamepad/switch_pro_controller_linux.cc
+++ /dev/null
@@ -1,30 +0,0 @@
-// 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_linux.h"
-
-#include "base/posix/eintr_wrapper.h"
-#include "device/gamepad/gamepad_standard_mappings.h"
-
-namespace device {
-
-SwitchProControllerLinux::SwitchProControllerLinux(int fd) : fd_(fd) {}
-
-SwitchProControllerLinux::~SwitchProControllerLinux() = default;
-
-size_t SwitchProControllerLinux::ReadInputReport(void* report) {
- DCHECK(report);
- ssize_t bytes_read = HANDLE_EINTR(read(fd_, report, kReportSize));
- return bytes_read < 0 ? 0 : static_cast<size_t>(bytes_read);
-}
-
-size_t SwitchProControllerLinux::WriteOutputReport(void* report,
- size_t report_length) {
- DCHECK(report);
- DCHECK_GE(report_length, 1U);
- ssize_t bytes_written = HANDLE_EINTR(write(fd_, report, report_length));
- return bytes_written < 0 ? 0 : static_cast<size_t>(bytes_written);
-}
-
-} // namespace device
diff --git a/device/gamepad/switch_pro_controller_linux.h b/device/gamepad/switch_pro_controller_linux.h
deleted file mode 100644
index d45e871..0000000
--- a/device/gamepad/switch_pro_controller_linux.h
+++ /dev/null
@@ -1,28 +0,0 @@
-// 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.
-
-#ifndef DEVICE_GAMEPAD_SWITCH_PRO_CONTROLLER_LINUX_
-#define DEVICE_GAMEPAD_SWITCH_PRO_CONTROLLER_LINUX_
-
-#include <memory>
-
-#include "device/gamepad/switch_pro_controller_base.h"
-
-namespace device {
-
-class SwitchProControllerLinux : public SwitchProControllerBase {
- public:
- SwitchProControllerLinux(int fd);
- ~SwitchProControllerLinux() override;
-
- size_t ReadInputReport(void* report) override;
- size_t WriteOutputReport(void* report, size_t report_length) override;
-
- private:
- int fd_ = -1;
-};
-
-} // namespace device
-
-#endif // DEVICE_GAMEPAD_SWITCH_PRO_CONTROLLER_LINUX_
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment