8 changed files with 1786 additions and 3 deletions
-
26src/common/input.h
-
5src/input_common/CMakeLists.txt
-
615src/input_common/drivers/joycon.cpp
-
107src/input_common/drivers/joycon.h
-
382src/input_common/helpers/joycon_driver.cpp
-
146src/input_common/helpers/joycon_driver.h
-
494src/input_common/helpers/joycon_protocol/joycon_types.h
-
14src/input_common/main.cpp
@ -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 |
||||
Write
Preview
Loading…
Cancel
Save
Reference in new issue