input_common: Initial skeleton for custom joycon driver
parent
475370c8f8
commit
d80e6c399b
@ -0,0 +1,615 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <fmt/format.h>
|
||||
|
||||
#include "common/param_package.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/drivers/joycon.h"
|
||||
#include "input_common/helpers/joycon_driver.h"
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
|
||||
LOG_INFO(Input, "Joycon driver Initialization started");
|
||||
const int init_res = SDL_hid_init();
|
||||
if (init_res == 0) {
|
||||
Setup();
|
||||
} else {
|
||||
LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res);
|
||||
}
|
||||
}
|
||||
|
||||
Joycons::~Joycons() {
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Joycons::Reset() {
|
||||
scan_thread = {};
|
||||
for (const auto& device : left_joycons) {
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
device->Stop();
|
||||
}
|
||||
for (const auto& device : right_joycons) {
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
device->Stop();
|
||||
}
|
||||
for (const auto& device : pro_joycons) {
|
||||
if (!device) {
|
||||
continue;
|
||||
}
|
||||
device->Stop();
|
||||
}
|
||||
SDL_hid_exit();
|
||||
}
|
||||
|
||||
void Joycons::Setup() {
|
||||
u32 port = 0;
|
||||
for (auto& device : left_joycons) {
|
||||
PreSetController(GetIdentifier(port, Joycon::ControllerType::Left));
|
||||
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||
}
|
||||
for (auto& device : right_joycons) {
|
||||
PreSetController(GetIdentifier(port, Joycon::ControllerType::Right));
|
||||
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||
}
|
||||
for (auto& device : pro_joycons) {
|
||||
PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro));
|
||||
device = std::make_shared<Joycon::JoyconDriver>(port++);
|
||||
}
|
||||
|
||||
if (!scan_thread_running) {
|
||||
scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); });
|
||||
}
|
||||
}
|
||||
|
||||
void Joycons::ScanThread(std::stop_token stop_token) {
|
||||
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||
Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
|
||||
scan_thread_running = true;
|
||||
while (!stop_token.stop_requested()) {
|
||||
SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0);
|
||||
SDL_hid_device_info* cur_dev = devs;
|
||||
|
||||
while (cur_dev) {
|
||||
if (IsDeviceNew(cur_dev)) {
|
||||
LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id,
|
||||
cur_dev->product_id);
|
||||
RegisterNewDevice(cur_dev);
|
||||
}
|
||||
cur_dev = cur_dev->next;
|
||||
}
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::seconds(5));
|
||||
}
|
||||
scan_thread_running = false;
|
||||
}
|
||||
|
||||
bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const {
|
||||
Joycon::ControllerType type{};
|
||||
Joycon::SerialNumber serial_number{};
|
||||
|
||||
const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
|
||||
if (result != Joycon::DriverResult::Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number);
|
||||
if (result2 != Joycon::DriverResult::Success) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto is_handle_identical = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!device->IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (device->GetHandleSerialNumber() != serial_number) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
// Check if device already exist
|
||||
switch (type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
for (const auto& device : left_joycons) {
|
||||
if (is_handle_identical(device)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
for (const auto& device : right_joycons) {
|
||||
if (is_handle_identical(device)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
case Joycon::ControllerType::Grip:
|
||||
for (const auto& device : pro_joycons) {
|
||||
if (is_handle_identical(device)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) {
|
||||
Joycon::ControllerType type{};
|
||||
auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type);
|
||||
auto handle = GetNextFreeHandle(type);
|
||||
if (handle == nullptr) {
|
||||
LOG_WARNING(Input, "No free handles available");
|
||||
return;
|
||||
}
|
||||
if (result == Joycon::DriverResult::Success) {
|
||||
result = handle->RequestDeviceAccess(device_info);
|
||||
}
|
||||
if (result == Joycon::DriverResult::Success) {
|
||||
LOG_WARNING(Input, "Initialize device");
|
||||
|
||||
std::function<void(Joycon::Battery)> on_battery_data;
|
||||
std::function<void(Joycon::Color)> on_button_data;
|
||||
std::function<void(int, f32)> on_stick_data;
|
||||
std::function<void(int, std::array<u8, 6>)> on_motion_data;
|
||||
std::function<void(s16)> on_ring_data;
|
||||
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||
|
||||
const std::size_t port = handle->GetDevicePort();
|
||||
handle->on_battery_data = {
|
||||
[this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }};
|
||||
handle->on_color_data = {
|
||||
[this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }};
|
||||
handle->on_button_data = {
|
||||
[this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }};
|
||||
handle->on_stick_data = {
|
||||
[this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }};
|
||||
handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) {
|
||||
OnMotionUpdate(port, type, id, value);
|
||||
}};
|
||||
handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }};
|
||||
handle->on_amiibo_data = {[this, port](const std::vector<u8>& amiibo_data) {
|
||||
OnAmiiboUpdate(port, amiibo_data);
|
||||
}};
|
||||
handle->InitializeDevice();
|
||||
}
|
||||
}
|
||||
|
||||
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetNextFreeHandle(
|
||||
Joycon::ControllerType type) const {
|
||||
|
||||
if (type == Joycon::ControllerType::Left) {
|
||||
for (const auto& device : left_joycons) {
|
||||
if (!device->IsConnected()) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == Joycon::ControllerType::Right) {
|
||||
for (const auto& device : right_joycons) {
|
||||
if (!device->IsConnected()) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
|
||||
for (const auto& device : pro_joycons) {
|
||||
if (!device->IsConnected()) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) {
|
||||
const auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
return handle->IsVibrationEnabled();
|
||||
}
|
||||
|
||||
Common::Input::VibrationError Joycons::SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) {
|
||||
const Joycon::VibrationValue native_vibration{
|
||||
.low_amplitude = vibration.low_amplitude,
|
||||
.low_frequency = vibration.low_frequency,
|
||||
.high_amplitude = vibration.high_amplitude,
|
||||
.high_frequency = vibration.high_amplitude,
|
||||
};
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return Common::Input::VibrationError::InvalidHandle;
|
||||
}
|
||||
|
||||
handle->SetVibration(native_vibration);
|
||||
return Common::Input::VibrationError::None;
|
||||
}
|
||||
|
||||
void Joycons::SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
return;
|
||||
}
|
||||
int led_config = led_status.led_1 ? 1 : 0;
|
||||
led_config += led_status.led_2 ? 2 : 0;
|
||||
led_config += led_status.led_3 ? 4 : 0;
|
||||
led_config += led_status.led_4 ? 8 : 0;
|
||||
|
||||
const auto result = handle->SetLedConfig(static_cast<u8>(led_config));
|
||||
if (result != Joycon::DriverResult::Success) {
|
||||
LOG_ERROR(Input, "Failed to set led config");
|
||||
}
|
||||
}
|
||||
|
||||
Common::Input::CameraError Joycons::SetCameraFormat(const PadIdentifier& identifier_,
|
||||
Common::Input::CameraFormat camera_format) {
|
||||
return Common::Input::CameraError::NotSupported;
|
||||
};
|
||||
|
||||
Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const {
|
||||
return Common::Input::NfcState::Success;
|
||||
};
|
||||
|
||||
Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_,
|
||||
const std::vector<u8>& data) {
|
||||
return Common::Input::NfcState::NotSupported;
|
||||
};
|
||||
|
||||
Common::Input::PollingError Joycons::SetPollingMode(const PadIdentifier& identifier,
|
||||
const Common::Input::PollingMode polling_mode) {
|
||||
auto handle = GetHandle(identifier);
|
||||
if (handle == nullptr) {
|
||||
LOG_ERROR(Input, "Invalid handle {}", identifier.port);
|
||||
return Common::Input::PollingError::InvalidHandle;
|
||||
}
|
||||
|
||||
switch (polling_mode) {
|
||||
case Common::Input::PollingMode::NFC:
|
||||
handle->SetNfcMode();
|
||||
break;
|
||||
case Common::Input::PollingMode::Active:
|
||||
handle->SetActiveMode();
|
||||
break;
|
||||
case Common::Input::PollingMode::Pasive:
|
||||
handle->SetPasiveMode();
|
||||
break;
|
||||
case Common::Input::PollingMode::Ring:
|
||||
handle->SetRingConMode();
|
||||
break;
|
||||
default:
|
||||
return Common::Input::PollingError::NotSupported;
|
||||
}
|
||||
|
||||
return Common::Input::PollingError::None;
|
||||
}
|
||||
|
||||
void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type,
|
||||
Joycon::Battery value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
if (value.charging != 0) {
|
||||
SetBattery(identifier, Common::Input::BatteryLevel::Charging);
|
||||
return;
|
||||
}
|
||||
|
||||
Common::Input::BatteryLevel battery{value.status.Value()};
|
||||
switch (value.status) {
|
||||
case 0:
|
||||
battery = Common::Input::BatteryLevel::Empty;
|
||||
break;
|
||||
case 1:
|
||||
battery = Common::Input::BatteryLevel::Critical;
|
||||
break;
|
||||
case 2:
|
||||
battery = Common::Input::BatteryLevel::Low;
|
||||
break;
|
||||
case 3:
|
||||
battery = Common::Input::BatteryLevel::Medium;
|
||||
break;
|
||||
case 4:
|
||||
default:
|
||||
battery = Common::Input::BatteryLevel::Full;
|
||||
break;
|
||||
}
|
||||
SetBattery(identifier, battery);
|
||||
}
|
||||
|
||||
void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type,
|
||||
const Joycon::Color& value) {}
|
||||
|
||||
void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
SetButton(identifier, id, value);
|
||||
}
|
||||
|
||||
void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
SetAxis(identifier, id, value);
|
||||
}
|
||||
|
||||
void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
|
||||
const Joycon::MotionData& value) {
|
||||
const auto identifier = GetIdentifier(port, type);
|
||||
BasicMotion motion_data{
|
||||
.gyro_x = value.gyro_x,
|
||||
.gyro_y = value.gyro_y,
|
||||
.gyro_z = value.gyro_z,
|
||||
.accel_x = value.accel_x,
|
||||
.accel_y = value.accel_y,
|
||||
.accel_z = value.accel_z,
|
||||
.delta_timestamp = 15000,
|
||||
};
|
||||
SetMotion(identifier, id, motion_data);
|
||||
}
|
||||
|
||||
void Joycons::OnRingConUpdate(f32 ring_data) {
|
||||
// To simplify ring detection it will always be mapped to an empty identifier for all
|
||||
// controllers
|
||||
constexpr PadIdentifier identifier = {
|
||||
.guid = Common::UUID{},
|
||||
.port = 0,
|
||||
.pad = 0,
|
||||
};
|
||||
SetAxis(identifier, 100, ring_data);
|
||||
}
|
||||
|
||||
void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data) {
|
||||
const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right);
|
||||
SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data});
|
||||
}
|
||||
|
||||
std::shared_ptr<Joycon::JoyconDriver> Joycons::GetHandle(PadIdentifier identifier) const {
|
||||
auto is_handle_active = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
|
||||
if (!device) {
|
||||
return false;
|
||||
}
|
||||
if (!device->IsConnected()) {
|
||||
return false;
|
||||
}
|
||||
if (device->GetDevicePort() == identifier.port) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
const auto type = static_cast<Joycon::ControllerType>(identifier.pad);
|
||||
if (type == Joycon::ControllerType::Left) {
|
||||
for (const auto& device : left_joycons) {
|
||||
if (is_handle_active(device)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == Joycon::ControllerType::Right) {
|
||||
for (const auto& device : right_joycons) {
|
||||
if (is_handle_active(device)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) {
|
||||
for (const auto& device : pro_joycons) {
|
||||
if (is_handle_active(device)) {
|
||||
return device;
|
||||
}
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const {
|
||||
return {
|
||||
.guid = Common::UUID{Common::InvalidUUID},
|
||||
.port = port,
|
||||
.pad = static_cast<std::size_t>(type),
|
||||
};
|
||||
}
|
||||
|
||||
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
|
||||
std::vector<Common::ParamPackage> devices{};
|
||||
|
||||
auto add_entry = [&](std::shared_ptr<Joycon::JoyconDriver> device) {
|
||||
if (!device) {
|
||||
return;
|
||||
}
|
||||
if (!device->IsConnected()) {
|
||||
return;
|
||||
}
|
||||
std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()),
|
||||
device->GetDevicePort());
|
||||
devices.emplace_back(Common::ParamPackage{
|
||||
{"engine", GetEngineName()},
|
||||
{"display", std::move(name)},
|
||||
{"port", std::to_string(device->GetDevicePort())},
|
||||
{"pad", std::to_string(static_cast<std::size_t>(device->GetHandleDeviceType()))},
|
||||
});
|
||||
};
|
||||
|
||||
for (const auto& controller : left_joycons) {
|
||||
add_entry(controller);
|
||||
}
|
||||
for (const auto& controller : right_joycons) {
|
||||
add_entry(controller);
|
||||
}
|
||||
for (const auto& controller : pro_joycons) {
|
||||
add_entry(controller);
|
||||
}
|
||||
|
||||
return devices;
|
||||
}
|
||||
|
||||
ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) {
|
||||
static constexpr std::array<std::pair<Settings::NativeButton::Values, Joycon::PadButton>, 20>
|
||||
switch_to_joycon_button = {
|
||||
std::pair{Settings::NativeButton::A, Joycon::PadButton::A},
|
||||
{Settings::NativeButton::B, Joycon::PadButton::B},
|
||||
{Settings::NativeButton::X, Joycon::PadButton::X},
|
||||
{Settings::NativeButton::Y, Joycon::PadButton::Y},
|
||||
{Settings::NativeButton::DLeft, Joycon::PadButton::Left},
|
||||
{Settings::NativeButton::DUp, Joycon::PadButton::Up},
|
||||
{Settings::NativeButton::DRight, Joycon::PadButton::Right},
|
||||
{Settings::NativeButton::DDown, Joycon::PadButton::Down},
|
||||
{Settings::NativeButton::SL, Joycon::PadButton::LeftSL},
|
||||
{Settings::NativeButton::SR, Joycon::PadButton::LeftSR},
|
||||
{Settings::NativeButton::L, Joycon::PadButton::L},
|
||||
{Settings::NativeButton::R, Joycon::PadButton::R},
|
||||
{Settings::NativeButton::ZL, Joycon::PadButton::ZL},
|
||||
{Settings::NativeButton::ZR, Joycon::PadButton::ZR},
|
||||
{Settings::NativeButton::Plus, Joycon::PadButton::Plus},
|
||||
{Settings::NativeButton::Minus, Joycon::PadButton::Minus},
|
||||
{Settings::NativeButton::Home, Joycon::PadButton::Home},
|
||||
{Settings::NativeButton::Screenshot, Joycon::PadButton::Capture},
|
||||
{Settings::NativeButton::LStick, Joycon::PadButton::StickL},
|
||||
{Settings::NativeButton::RStick, Joycon::PadButton::StickR},
|
||||
};
|
||||
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
ButtonMapping mapping{};
|
||||
for (const auto& [switch_button, joycon_button] : switch_to_joycon_button) {
|
||||
Common::ParamPackage button_params{};
|
||||
button_params.Set("engine", GetEngineName());
|
||||
button_params.Set("port", params.Get("port", 0));
|
||||
button_params.Set("button", static_cast<int>(joycon_button));
|
||||
mapping.insert_or_assign(switch_button, std::move(button_params));
|
||||
}
|
||||
|
||||
return mapping;
|
||||
}
|
||||
|
||||
AnalogMapping Joycons::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
AnalogMapping mapping = {};
|
||||
Common::ParamPackage left_analog_params;
|
||||
left_analog_params.Set("engine", GetEngineName());
|
||||
left_analog_params.Set("port", params.Get("port", 0));
|
||||
left_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::LeftStickX));
|
||||
left_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::LeftStickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params));
|
||||
Common::ParamPackage right_analog_params;
|
||||
right_analog_params.Set("engine", GetEngineName());
|
||||
right_analog_params.Set("port", params.Get("port", 0));
|
||||
right_analog_params.Set("axis_x", static_cast<int>(Joycon::PadAxes::RightStickX));
|
||||
right_analog_params.Set("axis_y", static_cast<int>(Joycon::PadAxes::RightStickY));
|
||||
mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) {
|
||||
if (!params.Has("port")) {
|
||||
return {};
|
||||
}
|
||||
|
||||
MotionMapping mapping = {};
|
||||
Common::ParamPackage left_motion_params;
|
||||
left_motion_params.Set("engine", GetEngineName());
|
||||
left_motion_params.Set("port", params.Get("port", 0));
|
||||
left_motion_params.Set("motion", 0);
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
|
||||
Common::ParamPackage right_Motion_params;
|
||||
right_Motion_params.Set("engine", GetEngineName());
|
||||
right_Motion_params.Set("port", params.Get("port", 0));
|
||||
right_Motion_params.Set("motion", 1);
|
||||
mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params));
|
||||
return mapping;
|
||||
}
|
||||
|
||||
Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const {
|
||||
const auto button = static_cast<Joycon::PadButton>(params.Get("button", 0));
|
||||
switch (button) {
|
||||
case Joycon::PadButton::Left:
|
||||
return Common::Input::ButtonNames::ButtonLeft;
|
||||
case Joycon::PadButton::Right:
|
||||
return Common::Input::ButtonNames::ButtonRight;
|
||||
case Joycon::PadButton::Down:
|
||||
return Common::Input::ButtonNames::ButtonDown;
|
||||
case Joycon::PadButton::Up:
|
||||
return Common::Input::ButtonNames::ButtonUp;
|
||||
case Joycon::PadButton::LeftSL:
|
||||
case Joycon::PadButton::RightSL:
|
||||
return Common::Input::ButtonNames::TriggerSL;
|
||||
case Joycon::PadButton::LeftSR:
|
||||
case Joycon::PadButton::RightSR:
|
||||
return Common::Input::ButtonNames::TriggerSR;
|
||||
case Joycon::PadButton::L:
|
||||
return Common::Input::ButtonNames::TriggerL;
|
||||
case Joycon::PadButton::R:
|
||||
return Common::Input::ButtonNames::TriggerR;
|
||||
case Joycon::PadButton::ZL:
|
||||
return Common::Input::ButtonNames::TriggerZL;
|
||||
case Joycon::PadButton::ZR:
|
||||
return Common::Input::ButtonNames::TriggerZR;
|
||||
case Joycon::PadButton::A:
|
||||
return Common::Input::ButtonNames::ButtonA;
|
||||
case Joycon::PadButton::B:
|
||||
return Common::Input::ButtonNames::ButtonB;
|
||||
case Joycon::PadButton::X:
|
||||
return Common::Input::ButtonNames::ButtonX;
|
||||
case Joycon::PadButton::Y:
|
||||
return Common::Input::ButtonNames::ButtonY;
|
||||
case Joycon::PadButton::Plus:
|
||||
return Common::Input::ButtonNames::ButtonPlus;
|
||||
case Joycon::PadButton::Minus:
|
||||
return Common::Input::ButtonNames::ButtonMinus;
|
||||
case Joycon::PadButton::Home:
|
||||
return Common::Input::ButtonNames::ButtonHome;
|
||||
case Joycon::PadButton::Capture:
|
||||
return Common::Input::ButtonNames::ButtonCapture;
|
||||
case Joycon::PadButton::StickL:
|
||||
return Common::Input::ButtonNames::ButtonStickL;
|
||||
case Joycon::PadButton::StickR:
|
||||
return Common::Input::ButtonNames::ButtonStickR;
|
||||
default:
|
||||
return Common::Input::ButtonNames::Undefined;
|
||||
}
|
||||
}
|
||||
|
||||
Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const {
|
||||
if (params.Has("button")) {
|
||||
return GetUIButtonName(params);
|
||||
}
|
||||
if (params.Has("axis")) {
|
||||
return Common::Input::ButtonNames::Value;
|
||||
}
|
||||
if (params.Has("motion")) {
|
||||
return Common::Input::ButtonNames::Engine;
|
||||
}
|
||||
|
||||
return Common::Input::ButtonNames::Invalid;
|
||||
}
|
||||
|
||||
std::string Joycons::JoyconName(Joycon::ControllerType type) const {
|
||||
switch (type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
return "Left Joycon";
|
||||
case Joycon::ControllerType::Right:
|
||||
return "Right Joycon";
|
||||
case Joycon::ControllerType::Pro:
|
||||
return "Pro Controller";
|
||||
case Joycon::ControllerType::Grip:
|
||||
return "Grip Controller";
|
||||
default:
|
||||
return "Unknow Joycon";
|
||||
}
|
||||
}
|
||||
} // namespace InputCommon
|
@ -0,0 +1,107 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
#include <SDL_hidapi.h>
|
||||
|
||||
#include "input_common/input_engine.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
using SerialNumber = std::array<u8, 15>;
|
||||
struct Battery;
|
||||
struct Color;
|
||||
struct MotionData;
|
||||
enum class ControllerType;
|
||||
enum class DriverResult;
|
||||
class JoyconDriver;
|
||||
} // namespace InputCommon::Joycon
|
||||
|
||||
namespace InputCommon {
|
||||
|
||||
class Joycons final : public InputCommon::InputEngine {
|
||||
public:
|
||||
explicit Joycons(const std::string& input_engine_);
|
||||
|
||||
~Joycons();
|
||||
|
||||
bool IsVibrationEnabled(const PadIdentifier& identifier) override;
|
||||
Common::Input::VibrationError SetVibration(
|
||||
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
|
||||
|
||||
void SetLeds(const PadIdentifier& identifier,
|
||||
const Common::Input::LedStatus& led_status) override;
|
||||
|
||||
Common::Input::CameraError SetCameraFormat(const PadIdentifier& identifier_,
|
||||
Common::Input::CameraFormat camera_format) override;
|
||||
|
||||
Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
|
||||
Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
|
||||
const std::vector<u8>& data) override;
|
||||
|
||||
Common::Input::PollingError SetPollingMode(
|
||||
const PadIdentifier& identifier, const Common::Input::PollingMode polling_mode) override;
|
||||
|
||||
/// Used for automapping features
|
||||
std::vector<Common::ParamPackage> GetInputDevices() const override;
|
||||
ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override;
|
||||
AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override;
|
||||
MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override;
|
||||
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||
|
||||
private:
|
||||
static constexpr std::size_t MaxSupportedControllers = 8;
|
||||
|
||||
/// For shutting down, clear all data, join all threads, release usb devices
|
||||
void Reset();
|
||||
|
||||
/// Registers controllers, clears all data and starts the scan thread
|
||||
void Setup();
|
||||
|
||||
/// Actively searchs for new devices
|
||||
void ScanThread(std::stop_token stop_token);
|
||||
|
||||
/// Returns true if device is valid and not registered
|
||||
bool IsDeviceNew(SDL_hid_device_info* device_info) const;
|
||||
|
||||
/// Tries to connect to the new device
|
||||
void RegisterNewDevice(SDL_hid_device_info* device_info);
|
||||
|
||||
/// Returns the next free handle
|
||||
std::shared_ptr<Joycon::JoyconDriver> GetNextFreeHandle(Joycon::ControllerType type) const;
|
||||
|
||||
void OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, Joycon::Battery value);
|
||||
void OnColorUpdate(std::size_t port, Joycon::ControllerType type, const Joycon::Color& value);
|
||||
void OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value);
|
||||
void OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value);
|
||||
void OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id,
|
||||
const Joycon::MotionData& value);
|
||||
void OnRingConUpdate(f32 ring_data);
|
||||
void OnAmiiboUpdate(std::size_t port, const std::vector<u8>& amiibo_data);
|
||||
|
||||
/// Returns a JoyconHandle corresponding to a PadIdentifier
|
||||
std::shared_ptr<Joycon::JoyconDriver> GetHandle(PadIdentifier identifier) const;
|
||||
|
||||
/// Returns a PadIdentifier corresponding to the port number
|
||||
PadIdentifier GetIdentifier(std::size_t port, Joycon::ControllerType type) const;
|
||||
|
||||
std::string JoyconName(std::size_t port) const;
|
||||
|
||||
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||
|
||||
/// Returns the name of the device in text format
|
||||
std::string JoyconName(Joycon::ControllerType type) const;
|
||||
|
||||
std::jthread scan_thread;
|
||||
bool scan_thread_running{};
|
||||
|
||||
// Joycon types are split by type to ease supporting dualjoycon configurations
|
||||
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> left_joycons{};
|
||||
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> right_joycons{};
|
||||
std::array<std::shared_ptr<Joycon::JoyconDriver>, MaxSupportedControllers> pro_joycons{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon
|
@ -0,0 +1,382 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "common/logging/log.h"
|
||||
#include "common/swap.h"
|
||||
#include "common/thread.h"
|
||||
#include "input_common/helpers/joycon_driver.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} {
|
||||
hidapi_handle = std::make_shared<JoyconHandle>();
|
||||
}
|
||||
|
||||
JoyconDriver::~JoyconDriver() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
void JoyconDriver::Stop() {
|
||||
is_connected = false;
|
||||
input_thread = {};
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) {
|
||||
std::scoped_lock lock{mutex};
|
||||
|
||||
handle_device_type = ControllerType::None;
|
||||
GetDeviceType(device_info, handle_device_type);
|
||||
if (handle_device_type == ControllerType::None) {
|
||||
return DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
hidapi_handle->handle =
|
||||
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
|
||||
std::memcpy(&handle_serial_number, device_info->serial_number, 15);
|
||||
if (!hidapi_handle->handle) {
|
||||
LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.",
|
||||
device_info->vendor_id, device_info->product_id);
|
||||
return DriverResult::HandleInUse;
|
||||
}
|
||||
SDL_hid_set_nonblocking(hidapi_handle->handle, 1);
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::InitializeDevice() {
|
||||
if (!hidapi_handle->handle) {
|
||||
return DriverResult::InvalidHandle;
|
||||
}
|
||||
std::scoped_lock lock{mutex};
|
||||
disable_input_thread = true;
|
||||
|
||||
// Reset Counters
|
||||
error_counter = 0;
|
||||
hidapi_handle->packet_counter = 0;
|
||||
|
||||
// Set HW default configuration
|
||||
vibration_enabled = true;
|
||||
motion_enabled = true;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
|
||||
gyro_performance = Joycon::GyroPerformance::HZ833;
|
||||
accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
|
||||
accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
|
||||
|
||||
// Initialize HW Protocols
|
||||
|
||||
// Get fixed joycon info
|
||||
supported_features = GetSupportedFeatures();
|
||||
|
||||
// Get Calibration data
|
||||
|
||||
// Set led status
|
||||
|
||||
// Apply HW configuration
|
||||
SetPollingMode();
|
||||
|
||||
// Start pooling for data
|
||||
is_connected = true;
|
||||
if (!input_thread_running) {
|
||||
input_thread =
|
||||
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
|
||||
}
|
||||
|
||||
disable_input_thread = false;
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
void JoyconDriver::InputThread(std::stop_token stop_token) {
|
||||
LOG_INFO(Input, "JC Adapter input thread started");
|
||||
Common::SetCurrentThreadName("JoyconInput");
|
||||
input_thread_running = true;
|
||||
|
||||
// Max update rate is 5ms, ensure we are always able to read a bit faster
|
||||
constexpr int ThreadDelay = 2;
|
||||
std::vector<u8> buffer(MaxBufferSize);
|
||||
|
||||
while (!stop_token.stop_requested()) {
|
||||
int status = 0;
|
||||
|
||||
if (!IsInputThreadValid()) {
|
||||
input_thread.request_stop();
|
||||
continue;
|
||||
}
|
||||
|
||||
// By disabling the input thread we can ensure custom commands will succeed as no package is
|
||||
// skipped
|
||||
if (!disable_input_thread) {
|
||||
status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(),
|
||||
ThreadDelay);
|
||||
} else {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay));
|
||||
}
|
||||
|
||||
if (IsPayloadCorrect(status, buffer)) {
|
||||
OnNewData(buffer);
|
||||
}
|
||||
|
||||
std::this_thread::yield();
|
||||
}
|
||||
|
||||
is_connected = false;
|
||||
input_thread_running = false;
|
||||
LOG_INFO(Input, "JC Adapter input thread stopped");
|
||||
}
|
||||
|
||||
void JoyconDriver::OnNewData(std::span<u8> buffer) {
|
||||
const auto report_mode = static_cast<InputReport>(buffer[0]);
|
||||
|
||||
switch (report_mode) {
|
||||
case InputReport::STANDARD_FULL_60HZ:
|
||||
ReadActiveMode(buffer);
|
||||
break;
|
||||
case InputReport::NFC_IR_MODE_60HZ:
|
||||
ReadNfcIRMode(buffer);
|
||||
break;
|
||||
case InputReport::SIMPLE_HID_MODE:
|
||||
ReadPassiveMode(buffer);
|
||||
break;
|
||||
default:
|
||||
LOG_ERROR(Input, "Report mode not Implemented {}", report_mode);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconDriver::SetPollingMode() {
|
||||
disable_input_thread = true;
|
||||
disable_input_thread = false;
|
||||
}
|
||||
|
||||
JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() {
|
||||
SupportedFeatures features{
|
||||
.passive = true,
|
||||
.motion = true,
|
||||
.vibration = true,
|
||||
};
|
||||
|
||||
if (device_type == ControllerType::Right) {
|
||||
features.nfc = true;
|
||||
features.irs = true;
|
||||
features.hidbus = true;
|
||||
}
|
||||
|
||||
if (device_type == ControllerType::Pro) {
|
||||
features.nfc = true;
|
||||
}
|
||||
return features;
|
||||
}
|
||||
|
||||
void JoyconDriver::ReadActiveMode(std::span<u8> buffer) {
|
||||
InputReportActive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportActive));
|
||||
|
||||
// Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion
|
||||
// experience
|
||||
const auto now = std::chrono::steady_clock::now();
|
||||
const auto new_delta_time =
|
||||
std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count();
|
||||
delta_time = static_cast<u64>((delta_time * 0.8f) + (new_delta_time * 0.2));
|
||||
last_update = now;
|
||||
|
||||
switch (device_type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
break;
|
||||
case Joycon::ControllerType::Grip:
|
||||
case Joycon::ControllerType::Dual:
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
|
||||
on_battery_data(data.battery_status);
|
||||
on_color_data(color);
|
||||
}
|
||||
|
||||
void JoyconDriver::ReadPassiveMode(std::span<u8> buffer) {
|
||||
InputReportPassive data{};
|
||||
memcpy(&data, buffer.data(), sizeof(InputReportPassive));
|
||||
|
||||
switch (device_type) {
|
||||
case Joycon::ControllerType::Left:
|
||||
break;
|
||||
case Joycon::ControllerType::Right:
|
||||
break;
|
||||
case Joycon::ControllerType::Pro:
|
||||
break;
|
||||
case Joycon::ControllerType::Grip:
|
||||
case Joycon::ControllerType::Dual:
|
||||
case Joycon::ControllerType::None:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void JoyconDriver::ReadNfcIRMode(std::span<u8> buffer) {
|
||||
// This mode is compatible with the active mode
|
||||
ReadActiveMode(buffer);
|
||||
|
||||
if (!nfc_enabled) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsInputThreadValid() const {
|
||||
if (!is_connected) {
|
||||
return false;
|
||||
}
|
||||
if (hidapi_handle->handle == nullptr) {
|
||||
return false;
|
||||
}
|
||||
// Controller is not responding. Terminate connection
|
||||
if (error_counter > MaxErrorCount) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) {
|
||||
if (status <= -1) {
|
||||
error_counter++;
|
||||
return false;
|
||||
}
|
||||
// There's no new data
|
||||
if (status == 0) {
|
||||
return false;
|
||||
}
|
||||
// No reply ever starts with zero
|
||||
if (buffer[0] == 0x00) {
|
||||
error_counter++;
|
||||
return false;
|
||||
}
|
||||
error_counter = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) {
|
||||
std::scoped_lock lock{mutex};
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) {
|
||||
std::scoped_lock lock{mutex};
|
||||
return DriverResult::NotSupported;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetPasiveMode() {
|
||||
motion_enabled = false;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = true;
|
||||
SetPollingMode();
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetActiveMode() {
|
||||
motion_enabled = false;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
SetPollingMode();
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetNfcMode() {
|
||||
motion_enabled = false;
|
||||
hidbus_enabled = false;
|
||||
nfc_enabled = true;
|
||||
passive_enabled = false;
|
||||
SetPollingMode();
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
DriverResult JoyconDriver::SetRingConMode() {
|
||||
motion_enabled = true;
|
||||
hidbus_enabled = true;
|
||||
nfc_enabled = false;
|
||||
passive_enabled = false;
|
||||
SetPollingMode();
|
||||
return DriverResult::Success;
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsConnected() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return is_connected;
|
||||
}
|
||||
|
||||
bool JoyconDriver::IsVibrationEnabled() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return vibration_enabled;
|
||||
}
|
||||
|
||||
FirmwareVersion JoyconDriver::GetDeviceVersion() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return version;
|
||||
}
|
||||
|
||||
Color JoyconDriver::GetDeviceColor() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return color;
|
||||
}
|
||||
|
||||
std::size_t JoyconDriver::GetDevicePort() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return port;
|
||||
}
|
||||
|
||||
ControllerType JoyconDriver::GetDeviceType() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return handle_device_type;
|
||||
}
|
||||
|
||||
ControllerType JoyconDriver::GetHandleDeviceType() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return handle_device_type;
|
||||
}
|
||||
|
||||
SerialNumber JoyconDriver::GetSerialNumber() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return serial_number;
|
||||
}
|
||||
|
||||
SerialNumber JoyconDriver::GetHandleSerialNumber() const {
|
||||
std::scoped_lock lock{mutex};
|
||||
return handle_serial_number;
|
||||
}
|
||||
|
||||
Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info,
|
||||
ControllerType& controller_type) {
|
||||
std::array<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
|
||||
std::pair<u32, Joycon::ControllerType>{0x2006, Joycon::ControllerType::Left},
|
||||
{0x2007, Joycon::ControllerType::Right},
|
||||
{0x2009, Joycon::ControllerType::Pro},
|
||||
{0x200E, Joycon::ControllerType::Grip},
|
||||
};
|
||||
constexpr u16 nintendo_vendor_id = 0x057e;
|
||||
|
||||
controller_type = Joycon::ControllerType::None;
|
||||
if (device_info->vendor_id != nintendo_vendor_id) {
|
||||
return Joycon::DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
for (const auto& [product_id, type] : supported_devices) {
|
||||
if (device_info->product_id == static_cast<u16>(product_id)) {
|
||||
controller_type = type;
|
||||
return Joycon::DriverResult::Success;
|
||||
}
|
||||
}
|
||||
return Joycon::DriverResult::UnsupportedControllerType;
|
||||
}
|
||||
|
||||
Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info,
|
||||
Joycon::SerialNumber& serial_number) {
|
||||
if (device_info->serial_number == nullptr) {
|
||||
return Joycon::DriverResult::Unknown;
|
||||
}
|
||||
std::memcpy(&serial_number, device_info->serial_number, 15);
|
||||
return Joycon::DriverResult::Success;
|
||||
}
|
||||
|
||||
} // namespace InputCommon::Joycon
|
@ -0,0 +1,146 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <functional>
|
||||
#include <mutex>
|
||||
#include <span>
|
||||
#include <thread>
|
||||
|
||||
#include "input_common/helpers/joycon_protocol/joycon_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
|
||||
class JoyconDriver final {
|
||||
public:
|
||||
explicit JoyconDriver(std::size_t port_);
|
||||
|
||||
~JoyconDriver();
|
||||
|
||||
DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
|
||||
DriverResult InitializeDevice();
|
||||
void Stop();
|
||||
|
||||
bool IsConnected() const;
|
||||
bool IsVibrationEnabled() const;
|
||||
|
||||
FirmwareVersion GetDeviceVersion() const;
|
||||
Color GetDeviceColor() const;
|
||||
std::size_t GetDevicePort() const;
|
||||
ControllerType GetDeviceType() const;
|
||||
ControllerType GetHandleDeviceType() const;
|
||||
SerialNumber GetSerialNumber() const;
|
||||
SerialNumber GetHandleSerialNumber() const;
|
||||
|
||||
DriverResult SetVibration(const VibrationValue& vibration);
|
||||
DriverResult SetLedConfig(u8 led_pattern);
|
||||
DriverResult SetPasiveMode();
|
||||
DriverResult SetActiveMode();
|
||||
DriverResult SetNfcMode();
|
||||
DriverResult SetRingConMode();
|
||||
|
||||
// Returns device type from hidapi handle
|
||||
static Joycon::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
|
||||
Joycon::ControllerType& controller_type);
|
||||
|
||||
// Returns serial number from hidapi handle
|
||||
static Joycon::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
|
||||
Joycon::SerialNumber& serial_number);
|
||||
|
||||
std::function<void(Battery)> on_battery_data;
|
||||
std::function<void(Color)> on_color_data;
|
||||
std::function<void(int, bool)> on_button_data;
|
||||
std::function<void(int, f32)> on_stick_data;
|
||||
std::function<void(int, MotionData)> on_motion_data;
|
||||
std::function<void(f32)> on_ring_data;
|
||||
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||
|
||||
private:
|
||||
struct SupportedFeatures {
|
||||
bool passive{};
|
||||
bool hidbus{};
|
||||
bool irs{};
|
||||
bool motion{};
|
||||
bool nfc{};
|
||||
bool vibration{};
|
||||
};
|
||||
|
||||
/// Main thread, actively request new data from the handle
|
||||
void InputThread(std::stop_token stop_token);
|
||||
|
||||
/// Called everytime a valid package arrives
|
||||
void OnNewData(std::span<u8> buffer);
|
||||
|
||||
/// Updates device configuration to enable or disable features
|
||||
void SetPollingMode();
|
||||
|
||||
/// Returns true if input thread is valid and doesn't need to be stopped
|
||||
bool IsInputThreadValid() const;
|
||||
|
||||
/// Returns true if the data should be interpreted. Otherwise the error counter is incremented
|
||||
bool IsPayloadCorrect(int status, std::span<const u8> buffer);
|
||||
|
||||
/// Returns a list of supported features that can be enabled on this device
|
||||
SupportedFeatures GetSupportedFeatures();
|
||||
|
||||
/// Handles data from passive packages
|
||||
void ReadPassiveMode(std::span<u8> buffer);
|
||||
|
||||
/// Handles data from active packages
|
||||
void ReadActiveMode(std::span<u8> buffer);
|
||||
|
||||
/// Handles data from nfc or ir packages
|
||||
void ReadNfcIRMode(std::span<u8> buffer);
|
||||
|
||||
// Protocol Features
|
||||
|
||||
// Connection status
|
||||
bool is_connected{};
|
||||
u64 delta_time;
|
||||
std::size_t error_counter{};
|
||||
std::shared_ptr<JoyconHandle> hidapi_handle = nullptr;
|
||||
std::chrono::time_point<std::chrono::steady_clock> last_update;
|
||||
|
||||
// External device status
|
||||
bool starlink_connected{};
|
||||
bool ring_connected{};
|
||||
bool amiibo_detected{};
|
||||
|
||||
// Harware configuration
|
||||
u8 leds{};
|
||||
ReportMode mode{};
|
||||
bool passive_enabled{}; // Low power mode, Ideal for multiple controllers at the same time
|
||||
bool hidbus_enabled{}; // External device support
|
||||
bool irs_enabled{}; // Infrared camera input
|
||||
bool motion_enabled{}; // Enables motion input
|
||||
bool nfc_enabled{}; // Enables Amiibo detection
|
||||
bool vibration_enabled{}; // Allows vibrations
|
||||
|
||||
// Calibration data
|
||||
GyroSensitivity gyro_sensitivity{};
|
||||
GyroPerformance gyro_performance{};
|
||||
AccelerometerSensitivity accelerometer_sensitivity{};
|
||||
AccelerometerPerformance accelerometer_performance{};
|
||||
JoyStickCalibration left_stick_calibration{};
|
||||
JoyStickCalibration right_stick_calibration{};
|
||||
MotionCalibration motion_calibration{};
|
||||
|
||||
// Fixed joycon info
|
||||
FirmwareVersion version{};
|
||||
Color color{};
|
||||
std::size_t port{};
|
||||
ControllerType device_type{}; // Device type reported by controller
|
||||
ControllerType handle_device_type{}; // Device type reported by hidapi
|
||||
SerialNumber serial_number{}; // Serial number reported by controller
|
||||
SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
|
||||
SupportedFeatures supported_features{};
|
||||
|
||||
// Thread related
|
||||
mutable std::mutex mutex;
|
||||
std::jthread input_thread;
|
||||
bool input_thread_running{};
|
||||
bool disable_input_thread{};
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
@ -0,0 +1,494 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
|
||||
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
|
||||
// https://github.com/CTCaer/jc_toolkit
|
||||
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <SDL_hidapi.h>
|
||||
|
||||
#include "common/bit_field.h"
|
||||
#include "common/common_funcs.h"
|
||||
#include "common/common_types.h"
|
||||
|
||||
namespace InputCommon::Joycon {
|
||||
constexpr u32 MaxErrorCount = 50;
|
||||
constexpr u32 MaxBufferSize = 60;
|
||||
constexpr u32 MaxResponseSize = 49;
|
||||
constexpr u32 MaxSubCommandResponseSize = 64;
|
||||
constexpr std::array<u8, 8> DefaultVibrationBuffer{0x0, 0x1, 0x40, 0x40, 0x0, 0x1, 0x40, 0x40};
|
||||
|
||||
using MacAddress = std::array<u8, 6>;
|
||||
using SerialNumber = std::array<u8, 15>;
|
||||
|
||||
enum class ControllerType {
|
||||
None,
|
||||
Left,
|
||||
Right,
|
||||
Pro,
|
||||
Grip,
|
||||
Dual,
|
||||
};
|
||||
|
||||
enum class PadAxes {
|
||||
LeftStickX,
|
||||
LeftStickY,
|
||||
RightStickX,
|
||||
RightStickY,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadMotion {
|
||||
LeftMotion,
|
||||
RightMotion,
|
||||
Undefined,
|
||||
};
|
||||
|
||||
enum class PadButton : u32 {
|
||||
Down = 0x000001,
|
||||
Up = 0x000002,
|
||||
Right = 0x000004,
|
||||
Left = 0x000008,
|
||||
LeftSR = 0x000010,
|
||||
LeftSL = 0x000020,
|
||||
L = 0x000040,
|
||||
ZL = 0x000080,
|
||||
Y = 0x000100,
|
||||
X = 0x000200,
|
||||
B = 0x000400,
|
||||
A = 0x000800,
|
||||
RightSR = 0x001000,
|
||||
RightSL = 0x002000,
|
||||
R = 0x004000,
|
||||
ZR = 0x008000,
|
||||
Minus = 0x010000,
|
||||
Plus = 0x020000,
|
||||
StickR = 0x040000,
|
||||
StickL = 0x080000,
|
||||
Home = 0x100000,
|
||||
Capture = 0x200000,
|
||||
};
|
||||
|
||||
enum class PasivePadButton : u32 {
|
||||
Down_A = 0x0001,
|
||||
Right_X = 0x0002,
|
||||
Left_B = 0x0004,
|
||||
Up_Y = 0x0008,
|
||||
SL = 0x0010,
|
||||
SR = 0x0020,
|
||||
Minus = 0x0100,
|
||||
Plus = 0x0200,
|
||||
StickL = 0x0400,
|
||||
StickR = 0x0800,
|
||||
Home = 0x1000,
|
||||
Capture = 0x2000,
|
||||
L_R = 0x4000,
|
||||
ZL_ZR = 0x8000,
|
||||
};
|
||||
|
||||
enum class OutputReport : u8 {
|
||||
RUMBLE_AND_SUBCMD = 0x01,
|
||||
FW_UPDATE_PKT = 0x03,
|
||||
RUMBLE_ONLY = 0x10,
|
||||
MCU_DATA = 0x11,
|
||||
USB_CMD = 0x80,
|
||||
};
|
||||
|
||||
enum class InputReport : u8 {
|
||||
SUBCMD_REPLY = 0x21,
|
||||
STANDARD_FULL_60HZ = 0x30,
|
||||
NFC_IR_MODE_60HZ = 0x31,
|
||||
SIMPLE_HID_MODE = 0x3F,
|
||||
INPUT_USB_RESPONSE = 0x81,
|
||||
};
|
||||
|
||||
enum class FeatureReport : u8 {
|
||||
Last_SUBCMD = 0x02,
|
||||
OTA_GW_UPGRADE = 0x70,
|
||||
SETUP_MEM_READ = 0x71,
|
||||
MEM_READ = 0x72,
|
||||
ERASE_MEM_SECTOR = 0x73,
|
||||
MEM_WRITE = 0x74,
|
||||
LAUNCH = 0x75,
|
||||
};
|
||||
|
||||
enum class SubCommand : u8 {
|
||||
STATE = 0x00,
|
||||
MANUAL_BT_PAIRING = 0x01,
|
||||
REQ_DEV_INFO = 0x02,
|
||||
SET_REPORT_MODE = 0x03,
|
||||
TRIGGERS_ELAPSED = 0x04,
|
||||
GET_PAGE_LIST_STATE = 0x05,
|
||||
SET_HCI_STATE = 0x06,
|
||||
RESET_PAIRING_INFO = 0x07,
|
||||
LOW_POWER_MODE = 0x08,
|
||||
SPI_FLASH_READ = 0x10,
|
||||
SPI_FLASH_WRITE = 0x11,
|
||||
RESET_MCU = 0x20,
|
||||
SET_MCU_CONFIG = 0x21,
|
||||
SET_MCU_STATE = 0x22,
|
||||
SET_PLAYER_LIGHTS = 0x30,
|
||||
GET_PLAYER_LIGHTS = 0x31,
|
||||
SET_HOME_LIGHT = 0x38,
|
||||
ENABLE_IMU = 0x40,
|
||||
SET_IMU_SENSITIVITY = 0x41,
|
||||
WRITE_IMU_REG = 0x42,
|
||||
READ_IMU_REG = 0x43,
|
||||
ENABLE_VIBRATION = 0x48,
|
||||
GET_REGULATED_VOLTAGE = 0x50,
|
||||
SET_EXTERNAL_CONFIG = 0x58,
|
||||
UNKNOWN_RINGCON = 0x59,
|
||||
UNKNOWN_RINGCON2 = 0x5A,
|
||||
UNKNOWN_RINGCON3 = 0x5C,
|
||||
};
|
||||
|
||||
enum class UsbSubCommand : u8 {
|
||||
CONN_STATUS = 0x01,
|
||||
HADSHAKE = 0x02,
|
||||
BAUDRATE_3M = 0x03,
|
||||
NO_TIMEOUT = 0x04,
|
||||
EN_TIMEOUT = 0x05,
|
||||
RESET = 0x06,
|
||||
PRE_HANDSHAKE = 0x91,
|
||||
SEND_UART = 0x92,
|
||||
};
|
||||
|
||||
enum class CalMagic : u8 {
|
||||
USR_MAGIC_0 = 0xB2,
|
||||
USR_MAGIC_1 = 0xA1,
|
||||
USRR_MAGI_SIZE = 2,
|
||||
};
|
||||
|
||||
enum class CalAddr {
|
||||
SERIAL_NUMBER = 0X6000,
|
||||
DEVICE_TYPE = 0X6012,
|
||||
COLOR_EXIST = 0X601B,
|
||||
FACT_LEFT_DATA = 0X603d,
|
||||
FACT_RIGHT_DATA = 0X6046,
|
||||
COLOR_DATA = 0X6050,
|
||||
FACT_IMU_DATA = 0X6020,
|
||||
USER_LEFT_MAGIC = 0X8010,
|
||||
USER_LEFT_DATA = 0X8012,
|
||||
USER_RIGHT_MAGIC = 0X801B,
|
||||
USER_RIGHT_DATA = 0X801D,
|
||||
USER_IMU_MAGIC = 0X8026,
|
||||
USER_IMU_DATA = 0X8028,
|
||||
};
|
||||
|
||||
enum class ReportMode : u8 {
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_DATA = 0x00,
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_CONFIGURATION = 0x01,
|
||||
ACTIVE_POLLING_NFC_IR_CAMERA_DATA_CONFIGURATION = 0x02,
|
||||
ACTIVE_POLLING_IR_CAMERA_DATA = 0x03,
|
||||
MCU_UPDATE_STATE = 0x23,
|
||||
STANDARD_FULL_60HZ = 0x30,
|
||||
NFC_IR_MODE_60HZ = 0x31,
|
||||
SIMPLE_HID_MODE = 0x3F,
|
||||
};
|
||||
|
||||
enum class GyroSensitivity : u8 {
|
||||
DPS250,
|
||||
DPS500,
|
||||
DPS1000,
|
||||
DPS2000, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerSensitivity : u8 {
|
||||
G8, // Default
|
||||
G4,
|
||||
G2,
|
||||
G16,
|
||||
};
|
||||
|
||||
enum class GyroPerformance : u8 {
|
||||
HZ833,
|
||||
HZ208, // Default
|
||||
};
|
||||
|
||||
enum class AccelerometerPerformance : u8 {
|
||||
HZ200,
|
||||
HZ100, // Default
|
||||
};
|
||||
|
||||
enum class MCUCommand : u8 {
|
||||
ConfigureMCU = 0x21,
|
||||
ConfigureIR = 0x23,
|
||||
};
|
||||
|
||||
enum class MCUSubCommand : u8 {
|
||||
SetMCUMode = 0x0,
|
||||
SetDeviceMode = 0x1,
|
||||
ReadDeviceMode = 0x02,
|
||||
WriteDeviceRegisters = 0x4,
|
||||
};
|
||||
|
||||
enum class MCUMode : u8 {
|
||||
Suspend = 0,
|
||||
Standby = 1,
|
||||
Ringcon = 3,
|
||||
NFC = 4,
|
||||
IR = 5,
|
||||
MaybeFWUpdate = 6,
|
||||
};
|
||||
|
||||
enum class MCURequest : u8 {
|
||||
GetMCUStatus = 1,
|
||||
GetNFCData = 2,
|
||||
GetIRData = 3,
|
||||
};
|
||||
|
||||
enum class MCUReport : u8 {
|
||||
Empty = 0x00,
|
||||
StateReport = 0x01,
|
||||
IRData = 0x03,
|
||||
BusyInitializing = 0x0b,
|
||||
IRStatus = 0x13,
|
||||
IRRegisters = 0x1b,
|
||||
NFCState = 0x2a,
|
||||
NFCReadData = 0x3a,
|
||||
EmptyAwaitingCmd = 0xff,
|
||||
};
|
||||
|
||||
enum class MCUPacketFlag : u8 {
|
||||
MorePacketsRemaining = 0x00,
|
||||
LastCommandPacket = 0x08,
|
||||
};
|
||||
|
||||
enum class NFCReadCommand : u8 {
|
||||
CancelAll = 0x00,
|
||||
StartPolling = 0x01,
|
||||
StopPolling = 0x02,
|
||||
StartWaitingRecieve = 0x04,
|
||||
Ntag = 0x06,
|
||||
Mifare = 0x0F,
|
||||
};
|
||||
|
||||
enum class NFCTagType : u8 {
|
||||
AllTags = 0x00,
|
||||
Ntag215 = 0x01,
|
||||
};
|
||||
|
||||
enum class DriverResult {
|
||||
Success,
|
||||
WrongReply,
|
||||
Timeout,
|
||||
UnsupportedControllerType,
|
||||
HandleInUse,
|
||||
ErrorReadingData,
|
||||
ErrorWritingData,
|
||||
NoDeviceDetected,
|
||||
InvalidHandle,
|
||||
NotSupported,
|
||||
Unknown,
|
||||
};
|
||||
|
||||
struct MotionSensorCalibration {
|
||||
s16 offset;
|
||||
s16 scale;
|
||||
};
|
||||
|
||||
struct MotionCalibration {
|
||||
std::array<MotionSensorCalibration, 3> accelerometer;
|
||||
std::array<MotionSensorCalibration, 3> gyro;
|
||||
};
|
||||
|
||||
// Basic motion data containing data from the sensors and a timestamp in microseconds
|
||||
struct MotionData {
|
||||
float gyro_x{};
|
||||
float gyro_y{};
|
||||
float gyro_z{};
|
||||
float accel_x{};
|
||||
float accel_y{};
|
||||
float accel_z{};
|
||||
u64 delta_timestamp{};
|
||||
};
|
||||
|
||||
struct JoyStickAxisCalibration {
|
||||
u16 max{1};
|
||||
u16 min{1};
|
||||
u16 center{0};
|
||||
};
|
||||
|
||||
struct JoyStickCalibration {
|
||||
JoyStickAxisCalibration x;
|
||||
JoyStickAxisCalibration y;
|
||||
};
|
||||
|
||||
struct RingCalibration {
|
||||
s16 default_value;
|
||||
s16 max_value;
|
||||
s16 min_value;
|
||||
};
|
||||
|
||||
struct Color {
|
||||
u32 body;
|
||||
u32 buttons;
|
||||
u32 left_grip;
|
||||
u32 right_grip;
|
||||
};
|
||||
|
||||
struct Battery {
|
||||
union {
|
||||
u8 raw{};
|
||||
|
||||
BitField<0, 4, u8> unknown;
|
||||
BitField<4, 1, u8> charging;
|
||||
BitField<5, 3, u8> status;
|
||||
};
|
||||
};
|
||||
|
||||
struct VibrationValue {
|
||||
f32 low_amplitude;
|
||||
f32 low_frequency;
|
||||
f32 high_amplitude;
|
||||
f32 high_frequency;
|
||||
};
|
||||
|
||||
struct JoyconHandle {
|
||||
SDL_hid_device* handle = nullptr;
|
||||
u8 packet_counter{};
|
||||
};
|
||||
|
||||
struct MCUConfig {
|
||||
MCUCommand command;
|
||||
MCUSubCommand sub_command;
|
||||
MCUMode mode;
|
||||
INSERT_PADDING_BYTES(0x22);
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(MCUConfig) == 0x26, "MCUConfig is an invalid size");
|
||||
|
||||
#pragma pack(push, 1)
|
||||
struct InputReportPassive {
|
||||
InputReport report_mode;
|
||||
u16 button_input;
|
||||
u8 stick_state;
|
||||
std::array<u8, 10> unknown_data;
|
||||
};
|
||||
static_assert(sizeof(InputReportPassive) == 0xE, "InputReportPassive is an invalid size");
|
||||
|
||||
struct InputReportActive {
|
||||
InputReport report_mode;
|
||||
u8 packet_id;
|
||||
Battery battery_status;
|
||||
std::array<u8, 3> button_input;
|
||||
std::array<u8, 3> left_stick_state;
|
||||
std::array<u8, 3> right_stick_state;
|
||||
u8 vibration_code;
|
||||
std::array<s16, 6 * 2> motion_input;
|
||||
INSERT_PADDING_BYTES(0x2);
|
||||
s16 ring_input;
|
||||
};
|
||||
static_assert(sizeof(InputReportActive) == 0x29, "InputReportActive is an invalid size");
|
||||
|
||||
struct InputReportNfcIr {
|
||||
InputReport report_mode;
|
||||
u8 packet_id;
|
||||
Battery battery_status;
|
||||
std::array<u8, 3> button_input;
|
||||
std::array<u8, 3> left_stick_state;
|
||||
std::array<u8, 3> right_stick_state;
|
||||
u8 vibration_code;
|
||||
std::array<s16, 6 * 2> motion_input;
|
||||
INSERT_PADDING_BYTES(0x4);
|
||||
};
|
||||
static_assert(sizeof(InputReportNfcIr) == 0x29, "InputReportNfcIr is an invalid size");
|
||||
#pragma pack(pop)
|
||||
|
||||
struct IMUCalibration {
|
||||
std::array<s16, 3> accelerometer_offset;
|
||||
std::array<s16, 3> accelerometer_scale;
|
||||
std::array<s16, 3> gyroscope_offset;
|
||||
std::array<s16, 3> gyroscope_scale;
|
||||
};
|
||||
static_assert(sizeof(IMUCalibration) == 0x18, "IMUCalibration is an invalid size");
|
||||
|
||||
struct NFCReadBlock {
|
||||
u8 start;
|
||||
u8 end;
|
||||
};
|
||||
static_assert(sizeof(NFCReadBlock) == 0x2, "NFCReadBlock is an invalid size");
|
||||
|
||||
struct NFCReadBlockCommand {
|
||||
u8 block_count{};
|
||||
std::array<NFCReadBlock, 4> blocks{};
|
||||
};
|
||||
static_assert(sizeof(NFCReadBlockCommand) == 0x9, "NFCReadBlockCommand is an invalid size");
|
||||
|
||||
struct NFCReadCommandData {
|
||||
u8 unknown;
|
||||
u8 uuid_length;
|
||||
u8 unknown_2;
|
||||
std::array<u8, 6> uid;
|
||||
NFCTagType tag_type;
|
||||
NFCReadBlockCommand read_block;
|
||||
};
|
||||
static_assert(sizeof(NFCReadCommandData) == 0x13, "NFCReadCommandData is an invalid size");
|
||||
|
||||
struct NFCPollingCommandData {
|
||||
u8 enable_mifare;
|
||||
u8 unknown_1;
|
||||
u8 unknown_2;
|
||||
u8 unknown_3;
|
||||
u8 unknown_4;
|
||||
};
|
||||
static_assert(sizeof(NFCPollingCommandData) == 0x05, "NFCPollingCommandData is an invalid size");
|
||||
|
||||
struct NFCRequestState {
|
||||
MCUSubCommand sub_command;
|
||||
NFCReadCommand command_argument;
|
||||
u8 packet_id;
|
||||
INSERT_PADDING_BYTES(0x1);
|
||||
MCUPacketFlag packet_flag;
|
||||
u8 data_length;
|
||||
union {
|
||||
std::array<u8, 0x1F> raw_data;
|
||||
NFCReadCommandData nfc_read;
|
||||
NFCPollingCommandData nfc_polling;
|
||||
};
|
||||
u8 crc;
|
||||
};
|
||||
static_assert(sizeof(NFCRequestState) == 0x26, "NFCRequestState is an invalid size");
|
||||
|
||||
struct FirmwareVersion {
|
||||
u8 major;
|
||||
u8 minor;
|
||||
};
|
||||
static_assert(sizeof(FirmwareVersion) == 0x2, "FirmwareVersion is an invalid size");
|
||||
|
||||
struct DeviceInfo {
|
||||
FirmwareVersion firmware;
|
||||
MacAddress mac_address;
|
||||
};
|
||||
static_assert(sizeof(DeviceInfo) == 0x8, "DeviceInfo is an invalid size");
|
||||
|
||||
struct MotionStatus {
|
||||
bool is_enabled;
|
||||
u64 delta_time;
|
||||
GyroSensitivity gyro_sensitivity;
|
||||
AccelerometerSensitivity accelerometer_sensitivity;
|
||||
};
|
||||
|
||||
struct RingStatus {
|
||||
bool is_enabled;
|
||||
s16 default_value;
|
||||
s16 max_value;
|
||||
s16 min_value;
|
||||
};
|
||||
|
||||
struct JoyconCallbacks {
|
||||
std::function<void(Battery)> on_battery_data;
|
||||
std::function<void(Color)> on_color_data;
|
||||
std::function<void(int, bool)> on_button_data;
|
||||
std::function<void(int, f32)> on_stick_data;
|
||||
std::function<void(int, const MotionData&)> on_motion_data;
|
||||
std::function<void(f32)> on_ring_data;
|
||||
std::function<void(const std::vector<u8>&)> on_amiibo_data;
|
||||
};
|
||||
|
||||
} // namespace InputCommon::Joycon
|
Loading…
Reference in New Issue