Compare commits

...

4 Commits

Author SHA1 Message Date
german77
c3589d89cc Add error handling 2022-04-23 19:37:19 -05:00
Narr the Reg
a1ed499ce8 Remove magic values 2022-04-23 19:37:19 -05:00
Narr the Reg
7c6d2a3068 settings: Add Enable SDL Joycon setting 2022-04-23 19:37:15 -05:00
Narr the Reg
d27a4b3e07 input_common: Implement custom joycon driver 2022-04-23 19:36:47 -05:00
13 changed files with 1733 additions and 15 deletions

View File

@@ -183,17 +183,31 @@ enum class ButtonNames {
Engine,
// This will display the button by value instead of the button name
Value,
// Joycon button names
ButtonLeft,
ButtonRight,
ButtonDown,
ButtonUp,
TriggerZ,
TriggerR,
TriggerL,
ButtonA,
ButtonB,
ButtonX,
ButtonY,
ButtonPlus,
ButtonMinus,
ButtonHome,
ButtonCapture,
ButtonStickL,
ButtonStickR,
TriggerL,
TriggerZL,
TriggerSL,
TriggerR,
TriggerZR,
TriggerSR,
// GC button names
TriggerZ,
ButtonStart,
// DS4 button names

View File

@@ -559,6 +559,7 @@ struct Values {
BasicSetting<bool> enable_raw_input{false, "enable_raw_input"};
BasicSetting<bool> controller_navigation{true, "controller_navigation"};
BasicSetting<bool> enable_sdl_joycons{false, "enable_sdl_joycons"};
Setting<bool> vibration_enabled{true, "vibration_enabled"};
Setting<bool> enable_accurate_vibrations{false, "enable_accurate_vibrations"};

View File

@@ -5,8 +5,6 @@ add_library(input_common STATIC
drivers/keyboard.h
drivers/mouse.cpp
drivers/mouse.h
drivers/sdl_driver.cpp
drivers/sdl_driver.h
drivers/tas_input.cpp
drivers/tas_input.h
drivers/touch_screen.cpp
@@ -53,11 +51,15 @@ endif()
if (ENABLE_SDL2)
target_sources(input_common PRIVATE
drivers/joycon.cpp
drivers/joycon.h
drivers/sdl_driver.cpp
drivers/sdl_driver.h
helpers/joycon_protocol.cpp
helpers/joycon_protocol.h
)
target_link_libraries(input_common PRIVATE SDL2)
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
target_link_libraries(input_common PRIVATE SDL2)
endif()
target_link_libraries(input_common PRIVATE usb)

View File

@@ -0,0 +1,592 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <fmt/format.h>
#include "common/param_package.h"
#include "common/settings.h"
#include "common/thread.h"
#include "input_common/drivers/joycon.h"
namespace InputCommon {
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
if (Settings::values.enable_sdl_joycons) {
// Avoid conflicting with SDL driver
return;
}
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::UpdateJoyconData(JoyconData& jc, std::span<const u8> buffer) {
switch (jc.type) {
case Joycon::ControllerType::Left:
UpdateLeftPadInput(jc, buffer);
break;
case Joycon::ControllerType::Right:
UpdateRightPadInput(jc, buffer);
break;
case Joycon::ControllerType::Pro:
UpdateProPadInput(jc, buffer);
break;
case Joycon::ControllerType::None:
break;
}
jc.raw_battery = buffer[2] >> 4;
}
std::string Joycons::JoyconName(std::size_t port) const {
switch (joycon_data[port].type) {
case Joycon::ControllerType::Left:
return "Left Joycon";
case Joycon::ControllerType::Right:
return "Right Joycon";
case Joycon::ControllerType::Pro:
return "Pro Controller";
default:
return "Unknow Joycon";
}
}
void Joycons::ResetDeviceType(std::size_t port) {
if (port > joycon_data.size()) {
return;
}
joycon_data[port].type = Joycon::ControllerType::None;
}
void Joycons::InputThread(std::stop_token stop_token) {
LOG_INFO(Input, "JC Adapter scan thread started");
Common::SetCurrentThreadName("yuzu:input:JoyconInputThread");
input_thread_running = true;
std::vector<u8> buffer(Joycon::max_resp_size);
while (!stop_token.stop_requested()) {
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
if (joycon_data[port].type == Joycon::ControllerType::None) {
continue;
}
const int status = SDL_hid_read_timeout(joycon_data[port].joycon_handle.handle,
buffer.data(), Joycon::max_resp_size, 15);
if (IsPayloadCorrect(status, buffer)) {
UpdateJoyconData(joycon_data[port], buffer);
}
}
std::this_thread::yield();
}
input_thread_running = false;
}
bool Joycons::IsPayloadCorrect(int status, std::span<const u8> buffer) {
if (status > 0) {
if (buffer[0] == 0x00) {
// Invalid response
LOG_DEBUG(Input, "Error reading payload");
if (input_error_counter++ > 20) {
LOG_ERROR(Input, "Timeout, Is the joycon connected?");
input_thread.request_stop();
}
}
return false;
}
input_error_counter = 0;
return true;
}
void Joycons::Setup() {
// Initialize all controllers as unplugged
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
PreSetController(GetIdentifier(port));
joycon_data[port].type = Joycon::ControllerType::None;
joycon_data[port].hd_rumble.high_amplitude = 0;
joycon_data[port].hd_rumble.low_amplitude = 0;
joycon_data[port].hd_rumble.high_frequency = 320.0f;
joycon_data[port].hd_rumble.low_frequency = 160.0f;
}
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) {
Common::SetCurrentThreadName("yuzu:input:JoyconScanThread");
scan_thread_running = true;
std::size_t port = 0;
// while (!stop_token.stop_requested()) {
LOG_INFO(Input, "Scanning for devices");
SDL_hid_device_info* devs = SDL_hid_enumerate(0x057e, 0x0);
SDL_hid_device_info* cur_dev = devs;
while (cur_dev) {
LOG_WARNING(Input, "Device Found");
LOG_WARNING(Input, "type : {:04X} {:04X}", cur_dev->vendor_id, cur_dev->product_id);
bool skip_device = false;
for (std::size_t i = 0; i < joycon_data.size(); ++i) {
Joycon::ControllerType type{};
const auto result = Joycon::GetDeviceType(cur_dev, type);
if (result != Joycon::ErrorCode::Success || type == joycon_data[i].type) {
LOG_WARNING(Input, "Device already exist, result = {}, type = {}", result, type);
skip_device = true;
break;
}
}
if (!skip_device) {
const auto result = Joycon::CheckDeviceAccess(joycon_data[port].joycon_handle, cur_dev);
if (result == Joycon::ErrorCode::Success) {
LOG_WARNING(Input, "Device verified and accessible");
++port;
}
}
cur_dev = cur_dev->next;
}
GetJCEndpoint();
// std::this_thread::sleep_for(std::chrono::seconds(2));
//}
scan_thread_running = false;
}
void Joycons::Reset() {
scan_thread = {};
input_thread = {};
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
joycon_data[port].type = Joycon::ControllerType::None;
if (joycon_data[port].joycon_handle.handle) {
joycon_data[port].joycon_handle.handle = nullptr;
joycon_data[port].joycon_handle.packet_counter = 0;
}
}
SDL_hid_exit();
}
void Joycons::GetJCEndpoint() {
// init joycons
for (std::size_t port = 0; port < joycon_data.size(); ++port) {
auto& joycon = joycon_data[port];
auto& handle = joycon.joycon_handle;
joycon.port = port;
if (!joycon.joycon_handle.handle) {
continue;
}
LOG_INFO(Input, "Initializing device {}", port);
Joycon::GetDeviceType(handle, joycon.type);
// joycon_data[port].mac= Joycon::GetMac();
// SetSerialNumber(joycon_data[port]);
// SetVersionNumber(joycon_data[port]);
// SetDeviceType(joycon_data[port]);
Joycon::GetFactoryCalibrationData(handle, joycon.calibration, joycon.type);
// GetColor(joycon_data[port]);
joycon.rumble_enabled = true;
joycon.imu_enabled = true;
joycon.gyro_sensitivity = Joycon::GyroSensitivity::DPS2000;
joycon.gyro_performance = Joycon::GyroPerformance::HZ833;
joycon.accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8;
joycon.accelerometer_performance = Joycon::AccelerometerPerformance::HZ100;
Joycon::SetLedConfig(handle, static_cast<u8>(port + 1));
Joycon::EnableRumble(handle, joycon.rumble_enabled);
Joycon::EnableImu(handle, joycon.imu_enabled);
Joycon::SetImuConfig(handle, joycon.gyro_sensitivity, joycon.gyro_performance,
joycon.accelerometer_sensitivity, joycon.accelerometer_performance);
Joycon::SetReportMode(handle, Joycon::ReportMode::STANDARD_FULL_60HZ);
}
if (!input_thread_running) {
input_thread =
std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); });
}
}
bool Joycons::DeviceConnected(std::size_t port) const {
if (port > joycon_data.size()) {
return false;
}
return joycon_data[port].type != Joycon::ControllerType::None;
}
f32 Joycons::GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const {
const f32 value = static_cast<f32>(raw_value - calibration.center);
if (value > 0.0f) {
return value / calibration.max;
}
return value / calibration.min;
}
void Joycons::UpdateLeftPadInput(JoyconData& jc, std::span<const u8> buffer) {
static constexpr std::array<Joycon::PadButton, 11> left_buttons{
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
Joycon::PadButton::Left, Joycon::PadButton::LeftSL, Joycon::PadButton::LeftSR,
Joycon::PadButton::L, Joycon::PadButton::ZL, Joycon::PadButton::Minus,
Joycon::PadButton::Capture, Joycon::PadButton::StickL,
};
const auto identifier = GetIdentifier(jc.port);
const u32 raw_button = static_cast<u32>(buffer[5] | ((buffer[4] & 0b00101001) << 16));
for (std::size_t i = 0; i < left_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(left_buttons[i])) != 0;
const int button = static_cast<int>(left_buttons[i]);
SetButton(identifier, button, button_status);
}
const u16 raw_left_axis_x = static_cast<u16>(buffer[6] | ((buffer[7] & 0xf) << 8));
const u16 raw_left_axis_y = static_cast<u16>((buffer[7] >> 4) | (buffer[8] << 4));
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, jc.calibration.left_stick.x);
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, jc.calibration.left_stick.y);
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX), left_axis_x);
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY), left_axis_y);
if (jc.imu_enabled) {
auto left_motion = GetMotionInput(buffer, jc.calibration.imu, jc.accelerometer_sensitivity,
jc.gyro_sensitivity);
// Rotate motion axis to the correct direction
left_motion.accel_y = -left_motion.accel_y;
left_motion.accel_z = -left_motion.accel_z;
left_motion.gyro_x = -left_motion.gyro_x;
SetMotion(identifier, static_cast<int>(PadMotion::LeftMotion), left_motion);
}
}
void Joycons::UpdateRightPadInput(JoyconData& jc, std::span<const u8> buffer) {
static constexpr std::array<Joycon::PadButton, 11> right_buttons{
Joycon::PadButton::Y, Joycon::PadButton::X, Joycon::PadButton::B,
Joycon::PadButton::A, Joycon::PadButton::RightSL, Joycon::PadButton::RightSR,
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
Joycon::PadButton::Home, Joycon::PadButton::StickR,
};
const auto identifier = GetIdentifier(jc.port);
const u32 raw_button = static_cast<u32>((buffer[3] << 8) | (buffer[4] << 16));
for (std::size_t i = 0; i < right_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(right_buttons[i])) != 0;
const int button = static_cast<int>(right_buttons[i]);
SetButton(identifier, button, button_status);
}
const u16 raw_right_axis_x = static_cast<u16>(buffer[9] | ((buffer[10] & 0xf) << 8));
const u16 raw_right_axis_y = static_cast<u16>((buffer[10] >> 4) | (buffer[11] << 4));
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, jc.calibration.right_stick.x);
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, jc.calibration.right_stick.y);
SetAxis(identifier, static_cast<int>(PadAxes::RightStickX), right_axis_x);
SetAxis(identifier, static_cast<int>(PadAxes::RightStickY), right_axis_y);
if (jc.imu_enabled) {
auto right_motion = GetMotionInput(buffer, jc.calibration.imu, jc.accelerometer_sensitivity,
jc.gyro_sensitivity);
// Rotate motion axis to the correct direction
right_motion.accel_x = -right_motion.accel_x;
right_motion.accel_y = -right_motion.accel_y;
right_motion.gyro_z = -right_motion.gyro_z;
SetMotion(identifier, static_cast<int>(PadMotion::RightMotion), right_motion);
}
}
void Joycons::UpdateProPadInput(JoyconData& jc, std::span<const u8> buffer) {
static constexpr std::array<Joycon::PadButton, 18> pro_buttons{
Joycon::PadButton::Down, Joycon::PadButton::Up, Joycon::PadButton::Right,
Joycon::PadButton::Left, Joycon::PadButton::L, Joycon::PadButton::ZL,
Joycon::PadButton::Minus, Joycon::PadButton::Capture, Joycon::PadButton::Y,
Joycon::PadButton::X, Joycon::PadButton::B, Joycon::PadButton::A,
Joycon::PadButton::R, Joycon::PadButton::ZR, Joycon::PadButton::Plus,
Joycon::PadButton::Home, Joycon::PadButton::StickL, Joycon::PadButton::StickR,
};
const auto identifier = GetIdentifier(jc.port);
const u32 raw_button = static_cast<u32>(buffer[5] | (buffer[3] << 8) | (buffer[4] << 16));
for (std::size_t i = 0; i < pro_buttons.size(); ++i) {
const bool button_status = (raw_button & static_cast<u32>(pro_buttons[i])) != 0;
const int button = static_cast<int>(pro_buttons[i]);
SetButton(identifier, button, button_status);
}
const u16 raw_left_axis_x = static_cast<u16>(buffer[6] | ((buffer[7] & 0xf) << 8));
const u16 raw_left_axis_y = static_cast<u16>((buffer[7] >> 4) | (buffer[8] << 4));
const u16 raw_right_axis_x = static_cast<u16>(buffer[9] | ((buffer[10] & 0xf) << 8));
const u16 raw_right_axis_y = static_cast<u16>((buffer[10] >> 4) | (buffer[11] << 4));
const f32 left_axis_x = GetAxisValue(raw_left_axis_x, jc.calibration.left_stick.x);
const f32 left_axis_y = GetAxisValue(raw_left_axis_y, jc.calibration.left_stick.y);
const f32 right_axis_x = GetAxisValue(raw_right_axis_x, jc.calibration.right_stick.x);
const f32 right_axis_y = GetAxisValue(raw_right_axis_y, jc.calibration.right_stick.y);
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX), left_axis_x);
SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY), left_axis_y);
SetAxis(identifier, static_cast<int>(PadAxes::RightStickX), right_axis_x);
SetAxis(identifier, static_cast<int>(PadAxes::RightStickY), right_axis_y);
if (jc.imu_enabled) {
auto pro_motion = GetMotionInput(buffer, jc.calibration.imu, jc.accelerometer_sensitivity,
jc.gyro_sensitivity);
pro_motion.accel_y = -pro_motion.accel_y;
pro_motion.accel_z = -pro_motion.accel_z;
SetMotion(identifier, static_cast<int>(PadMotion::ProMotion), pro_motion);
}
}
f32 Joycons::GetAccelerometerValue(s16 raw, Joycon::ImuSensorCalibration cal,
Joycon::AccelerometerSensitivity sen) const {
const f32 value = raw * (1.0f / (cal.scale - cal.offset)) * 4;
switch (sen) {
case Joycon::AccelerometerSensitivity::G2:
return value / 4.0f;
case Joycon::AccelerometerSensitivity::G4:
return value / 2.0f;
case Joycon::AccelerometerSensitivity::G8:
return value;
case Joycon::AccelerometerSensitivity::G16:
return value * 2.0f;
}
return value;
}
f32 Joycons::GetGyroValue(s16 raw, Joycon::ImuSensorCalibration cal,
Joycon::GyroSensitivity sen) const {
const f32 value = (raw - cal.offset) * (936.0f / (cal.scale - cal.offset)) / 360.0f;
switch (sen) {
case Joycon::GyroSensitivity::DPS250:
return value / 8.0f;
case Joycon::GyroSensitivity::DPS500:
return value / 4.0f;
case Joycon::GyroSensitivity::DPS1000:
return value / 2.0f;
case Joycon::GyroSensitivity::DPS2000:
return value;
}
return value;
}
s16 Joycons::GetRawIMUValues(std::size_t sensor, size_t axis, std::span<const u8> buffer) const {
const size_t offset = (sensor * 6) + (axis * 2);
return static_cast<s16>(buffer[13 + offset] | (buffer[14 + offset] << 8));
}
BasicMotion Joycons::GetMotionInput(std::span<const u8> buffer, Joycon::ImuCalibration calibration,
Joycon::AccelerometerSensitivity accel_sensitivity,
Joycon::GyroSensitivity gyro_sensitivity) const {
std::array<BasicMotion, 3> motion_data{};
for (std::size_t sample = 0; sample < motion_data.size(); ++sample) {
auto& motion = motion_data[sample];
const auto& accel_cal = calibration.accelerometer;
const auto& gyro_cal = calibration.gyro;
const std::size_t sample_index = sample * 2;
const s16 raw_accel_x = GetRawIMUValues(sample_index, 1, buffer);
const s16 raw_accel_y = GetRawIMUValues(sample_index, 0, buffer);
const s16 raw_accel_z = GetRawIMUValues(sample_index, 2, buffer);
const s16 raw_gyro_x = GetRawIMUValues(sample_index + 1, 1, buffer);
const s16 raw_gyro_y = GetRawIMUValues(sample_index + 1, 0, buffer);
const s16 raw_gyro_z = GetRawIMUValues(sample_index + 1, 2, buffer);
motion.delta_timestamp = 15000;
motion.accel_x = GetAccelerometerValue(raw_accel_x, accel_cal[1], accel_sensitivity);
motion.accel_y = GetAccelerometerValue(raw_accel_y, accel_cal[0], accel_sensitivity);
motion.accel_z = GetAccelerometerValue(raw_accel_z, accel_cal[2], accel_sensitivity);
motion.gyro_x = GetGyroValue(raw_gyro_x, gyro_cal[1], gyro_sensitivity);
motion.gyro_y = GetGyroValue(raw_gyro_y, gyro_cal[0], gyro_sensitivity);
motion.gyro_z = GetGyroValue(raw_gyro_z, gyro_cal[2], gyro_sensitivity);
}
// TODO(German77): Return all three samples data
return motion_data[0];
}
Common::Input::VibrationError Joycons::SetRumble(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,
};
Joycon::SetVibration(GetHandle(identifier), native_vibration);
return Common::Input::VibrationError::None;
}
Joycon::JoyconHandle& Joycons::GetHandle(PadIdentifier identifier) {
return joycon_data[identifier.port].joycon_handle;
}
PadIdentifier Joycons::GetIdentifier(std::size_t port) const {
return {
.guid = Common::UUID{Common::InvalidUUID},
.port = port,
.pad = 0,
};
}
std::vector<Common::ParamPackage> Joycons::GetInputDevices() const {
std::vector<Common::ParamPackage> devices;
for (const auto& controller : joycon_data) {
if (DeviceConnected(controller.port)) {
std::string name = fmt::format("{} {}", JoyconName(controller.port), controller.port);
devices.emplace_back(Common::ParamPackage{
{"engine", GetEngineName()},
{"display", std::move(name)},
{"port", std::to_string(controller.port)},
});
}
}
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>(PadAxes::LeftStickX));
left_analog_params.Set("axis_y", static_cast<int>(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>(PadAxes::RightStickX));
right_analog_params.Set("axis_y", static_cast<int>(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;
}
} // namespace InputCommon

View File

@@ -0,0 +1,126 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#pragma once
#include <span>
#include <thread>
#include "input_common/helpers/joycon_protocol.h"
#include "input_common/input_engine.h"
namespace InputCommon {
class Joycons final : public InputCommon::InputEngine {
public:
explicit Joycons(const std::string& input_engine_);
~Joycons();
/// 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;
Common::Input::VibrationError SetRumble(
const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override;
private:
enum class PadAxes {
LeftStickX,
LeftStickY,
RightStickX,
RightStickY,
Undefined,
};
enum class PadMotion {
LeftMotion,
RightMotion,
ProMotion,
Undefined,
};
struct JoyconData {
Joycon::JoyconHandle joycon_handle{nullptr, 0};
// Harware config
Joycon::GyroSensitivity gyro_sensitivity;
Joycon::GyroPerformance gyro_performance;
Joycon::AccelerometerSensitivity accelerometer_sensitivity;
Joycon::AccelerometerPerformance accelerometer_performance;
Joycon::ReportMode mode;
bool imu_enabled;
bool rumble_enabled;
u8 leds;
// Fixed value info
f32 version;
std::array<u8, 6> mac;
Joycon::CalibrationData calibration;
Joycon::ControllerType type;
std::array<u8, 15> serial_number;
Joycon::Color color;
std::size_t port;
// Polled data
u32 raw_temperature;
u8 raw_battery;
Common::Input::VibrationStatus hd_rumble;
};
void InputThread(std::stop_token stop_token);
void ScanThread(std::stop_token stop_token);
/// Resets status of device connected to port
void ResetDeviceType(std::size_t port);
/// Captures JC Adapter endpoint address,
void GetJCEndpoint();
/// For shutting down, clear all data, join all threads, release usb
void Reset();
bool IsPayloadCorrect(int status, std::span<const u8> buffer);
/// For use in initialization, querying devices to find the adapter
void Setup();
bool DeviceConnected(std::size_t port) const;
void UpdateJoyconData(JoyconData& jc, std::span<const u8> buffer);
void UpdateLeftPadInput(JoyconData& jc, std::span<const u8> buffer);
void UpdateRightPadInput(JoyconData& jc, std::span<const u8> buffer);
void UpdateProPadInput(JoyconData& jc, std::span<const u8> buffer);
// Reads gyro and accelerometer values
f32 GetAxisValue(u16 raw_value, Joycon::JoyStickAxisCalibration calibration) const;
f32 GetAccelerometerValue(s16 raw_value, Joycon::ImuSensorCalibration cal,
Joycon::AccelerometerSensitivity sen) const;
f32 GetGyroValue(s16 raw_value, Joycon::ImuSensorCalibration cal,
Joycon::GyroSensitivity sen) const;
s16 GetRawIMUValues(size_t sensor, size_t axis, std::span<const u8> buffer) const;
BasicMotion GetMotionInput(std::span<const u8> buffer, Joycon::ImuCalibration calibration,
Joycon::AccelerometerSensitivity accel_sensitivity,
Joycon::GyroSensitivity gyro_sensitivity) const;
Joycon::JoyconHandle& GetHandle(PadIdentifier identifier);
PadIdentifier GetIdentifier(std::size_t port) const;
std::string JoyconName(std::size_t port) const;
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
std::jthread input_thread;
std::jthread scan_thread;
bool input_thread_running{};
bool scan_thread_running{};
u8 input_error_counter{};
std::array<JoyconData, 8> joycon_data{};
};
} // namespace InputCommon

View File

@@ -410,9 +410,11 @@ SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_en
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1");
SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and
// not a generic one
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
if (Settings::values.enable_sdl_joycons) {
// Use hidapi driver for joycons. This will allow joycons to be detected as a GameController
// and not a generic one
SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1");
}
// Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native
// driver on Linux.

View File

@@ -0,0 +1,479 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#include <math.h>
#include "common/logging/log.h"
#include "input_common/helpers/joycon_protocol.h"
namespace InputCommon::Joycon {
u8 GetCounter(JoyconHandle& joycon_handle) {
joycon_handle.packet_counter = (joycon_handle.packet_counter + 1) & 0x0F;
return joycon_handle.packet_counter;
}
ErrorCode CheckDeviceAccess(JoyconHandle& joycon_handle, SDL_hid_device_info* device_info) {
ControllerType controller_type{ControllerType::None};
const auto result = GetDeviceType(device_info, controller_type);
if (result != ErrorCode::Success || controller_type == ControllerType::None) {
return ErrorCode::UnsupportedControllerType;
}
joycon_handle.handle =
SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number);
if (!joycon_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 ErrorCode::HandleInUse;
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return ErrorCode::Success;
}
f32 EncodeRumbleAmplification(f32 amplification) {
if (amplification < 0.01182) {
return roundf(powf(amplification, 1.7f) * 7561);
} else if (amplification < 0.11249) {
return roundf((logf(amplification) * 11.556f) + 55.3f);
} else if (amplification < 0.22498) {
return roundf((log2f(amplification) * 32) + 131);
}
return roundf((log2f(amplification) * 64) + 200);
}
ErrorCode SetVibration(JoyconHandle& joycon_handle, VibrationValue vibration) {
std::vector<u8> buffer(Joycon::max_resp_size);
buffer[0] = static_cast<u8>(Joycon::OutputReport::RUMBLE_ONLY);
buffer[1] = GetCounter(joycon_handle);
if (vibration.high_amplitude == 0.0f && vibration.low_amplitude == 0.0f) {
for (std::size_t i = 0; i < Joycon::default_buffer.size(); ++i) {
buffer[2 + i] = Joycon::default_buffer[i];
}
return SendData(joycon_handle, buffer, max_resp_size);
}
const u16 encoded_hf =
static_cast<u16>(roundf(128 * log2f(vibration.high_frequency * 0.1f)) - 0x180);
const u8 encoded_lf =
static_cast<u8>(roundf(32 * log2f(vibration.low_frequency * 0.1f)) - 0x40);
const u8 encoded_hamp = static_cast<u8>(EncodeRumbleAmplification(vibration.high_amplitude));
const u8 encoded_lamp = static_cast<u8>(EncodeRumbleAmplification(vibration.low_amplitude));
for (u8 i = 0; i < 2; ++i) {
const u8 amplitude = i == 0 ? encoded_lamp : encoded_hamp;
const u8 offset = i * 4;
u32 encoded_amp = amplitude >> 1;
const u32 parity = (encoded_amp % 2) * 0x80;
encoded_amp >>= 1;
encoded_amp += 0x40;
buffer[2 + offset] = static_cast<u8>(encoded_hf & 0xff);
buffer[3 + offset] = static_cast<u8>((encoded_hf >> 8) & 0xff);
buffer[4 + offset] = static_cast<u8>(encoded_lf & 0xff);
buffer[3 + offset] |= static_cast<u8>(amplitude);
buffer[4 + offset] |= static_cast<u8>(parity);
buffer[5 + offset] = static_cast<u8>(encoded_amp);
}
return SendData(joycon_handle, buffer, max_resp_size);
}
ErrorCode SetImuConfig(JoyconHandle& joycon_handle, GyroSensitivity gsen, GyroPerformance gfrec,
AccelerometerSensitivity asen, AccelerometerPerformance afrec) {
const std::vector<u8> buffer{static_cast<u8>(gsen), static_cast<u8>(asen),
static_cast<u8>(gfrec), static_cast<u8>(afrec)};
std::vector<u8> output;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result = SendSubCommand(joycon_handle, SubCommand::SET_IMU_SENSITIVITY, buffer,
buffer.size(), output);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return result;
}
ErrorCode SetLedConfig(JoyconHandle& joycon_handle, u8 leds) {
const std::vector<u8> buffer{leds};
std::vector<u8> output;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result =
SendSubCommand(joycon_handle, SubCommand::SET_PLAYER_LIGHTS, buffer, buffer.size(), output);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return result;
}
ErrorCode SetReportMode(JoyconHandle& joycon_handle, ReportMode report_mode) {
const std::vector<u8> buffer{static_cast<u8>(report_mode)};
std::vector<u8> output;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result =
SendSubCommand(joycon_handle, SubCommand::SET_REPORT_MODE, buffer, buffer.size(), output);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return result;
}
ErrorCode GetColor(JoyconHandle& joycon_handle, Color& color) {
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result = ReadSPI(joycon_handle, Joycon::CalAddr::COLOR_DATA, buffer, 12);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
if (result != ErrorCode::Success) {
return result;
}
color.body = static_cast<u32>((buffer[2] << 16) | (buffer[1] << 8) | buffer[0]);
color.buttons = static_cast<u32>((buffer[5] << 16) | (buffer[4] << 8) | buffer[3]);
color.left_grip = static_cast<u32>((buffer[8] << 16) | (buffer[7] << 8) | buffer[6]);
color.right_grip = static_cast<u32>((buffer[11] << 16) | (buffer[10] << 8) | buffer[9]);
return ErrorCode::Success;
}
ErrorCode GetMacAddress(JoyconHandle& joycon_handle, MacAddress& mac_address) {
wchar_t buffer[255];
const auto result =
SDL_hid_get_serial_number_string(joycon_handle.handle, buffer, std::size(buffer));
if (result == -1) {
LOG_ERROR(Input, "Unable to get mac address {}", result);
return ErrorCode::ErrorReadingData;
}
for (std::size_t i = 0; i < mac_address.size(); ++i) {
wchar_t value[3] = {buffer[i * 2], buffer[(i * 2) + 1]};
mac_address[i] = static_cast<u8>(std::stoi(value, 0, 16));
}
return ErrorCode::Success;
}
ErrorCode GetSerialNumber(JoyconHandle& joycon_handle, SerialNumber& serial_number) {
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result = ReadSPI(joycon_handle, Joycon::CalAddr::SERIAL_NUMBER, buffer, 16);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
if (result != ErrorCode::Success) {
return result;
}
for (std::size_t i = 0; i < serial_number.size(); ++i) {
serial_number[i] = buffer[i + 1];
}
return ErrorCode::Success;
}
ErrorCode GetDeviceType(SDL_hid_device_info* device_info, ControllerType& controller_type) {
constexpr u16 nintendo_vendor_id = 0x057e;
constexpr u16 left_joycon_id = 0x2006;
constexpr u16 right_joycon_id = 0x2007;
constexpr u16 pro_controller_id = 0x2009;
if (device_info->vendor_id != nintendo_vendor_id) {
return ErrorCode::UnsupportedControllerType;
}
switch (device_info->product_id) {
case left_joycon_id:
controller_type = ControllerType::Left;
break;
case right_joycon_id:
controller_type = ControllerType::Right;
break;
case pro_controller_id:
controller_type = ControllerType::Pro;
break;
default:
controller_type = ControllerType::None;
break;
}
return ErrorCode::Success;
}
ErrorCode GetDeviceType(JoyconHandle& joycon_handle, ControllerType& controller_type) {
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result = ReadSPI(joycon_handle, Joycon::CalAddr::DEVICE_TYPE, buffer, 1);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
if (result != ErrorCode::Success) {
return result;
}
controller_type = static_cast<Joycon::ControllerType>(buffer[0]);
return ErrorCode::Success;
}
ErrorCode GetVersionNumber([[maybe_unused]] JoyconHandle& joycon_handle, FirmwareVersion& version) {
// Not implemented
version = FirmwareVersion::Rev_0;
return ErrorCode::Success;
}
ErrorCode GetLeftJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
bool is_factory_calibration) {
std::vector<u8> buffer;
ErrorCode result{ErrorCode::Unknown};
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
if (is_factory_calibration) {
result = ReadSPI(joycon_handle, CalAddr::FACT_LEFT_DATA, buffer, 9);
} else {
result = ReadSPI(joycon_handle, CalAddr::USER_LEFT_DATA, buffer, 9);
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
if (result != ErrorCode::Success) {
return result;
}
calibration.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
calibration.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
calibration.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
calibration.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
calibration.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
calibration.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
// Nintendo fix for drifting stick
// result = ReadSPI(0x60, 0x86 ,buffer, 16);
// calibration.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
return ErrorCode::Success;
}
ErrorCode GetRightJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
bool is_factory_calibration) {
std::vector<u8> buffer;
ErrorCode result{ErrorCode::Unknown};
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
if (is_factory_calibration) {
result = ReadSPI(joycon_handle, CalAddr::FACT_RIGHT_DATA, buffer, 9);
} else {
result = ReadSPI(joycon_handle, CalAddr::USER_RIGHT_DATA, buffer, 9);
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
if (result != ErrorCode::Success) {
return result;
}
calibration.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
calibration.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
calibration.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
calibration.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
calibration.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
calibration.y.max = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
// Nintendo fix for drifting stick
// buffer = ReadSPI(0x60, 0x98 , 16);
// joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
return ErrorCode::Success;
}
ErrorCode GetImuCalibration(JoyconHandle& joycon_handle, ImuCalibration& calibration,
bool is_factory_calibration) {
std::vector<u8> buffer;
ErrorCode result{ErrorCode::Unknown};
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
if (is_factory_calibration) {
result = ReadSPI(joycon_handle, CalAddr::FACT_IMU_DATA, buffer, 24);
} else {
result = ReadSPI(joycon_handle, CalAddr::USER_IMU_DATA, buffer, 24);
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
if (result != ErrorCode::Success) {
return result;
}
for (std::size_t i = 0; i < calibration.accelerometer.size(); ++i) {
const std::size_t index = i * 2;
calibration.accelerometer[i].offset =
static_cast<s16>(buffer[index] | (buffer[index + 1] << 8));
calibration.accelerometer[i].scale =
static_cast<s16>(buffer[index + 6] | (buffer[index + 7] << 8));
}
for (std::size_t i = 0; i < calibration.gyro.size(); ++i) {
const std::size_t index = i * 2;
calibration.gyro[i].offset =
static_cast<s16>(buffer[index + 12] | (buffer[index + 13] << 8));
calibration.gyro[i].scale =
static_cast<s16>(buffer[index + 18] | (buffer[index + 19] << 8));
}
return ErrorCode::Success;
}
ErrorCode GetUserCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
ControllerType controller_type) {
ErrorCode result{ErrorCode::Unknown};
switch (controller_type) {
case ControllerType::Left:
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, false);
if (result != ErrorCode::Success) {
return result;
}
break;
case ControllerType::Right:
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, false);
if (result != ErrorCode::Success) {
return result;
}
break;
case Joycon::ControllerType::Pro:
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, false);
if (result != ErrorCode::Success) {
return result;
}
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, false);
if (result != ErrorCode::Success) {
return result;
}
break;
default:
return ErrorCode::UnsupportedControllerType;
}
result = GetImuCalibration(joycon_handle, calibration_data.imu, false);
if (result != ErrorCode::Success) {
return result;
}
return ErrorCode::Success;
}
ErrorCode GetFactoryCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
ControllerType controller_type) {
ErrorCode result{ErrorCode::Unknown};
switch (controller_type) {
case ControllerType::Left:
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, true);
if (result != ErrorCode::Success) {
return result;
}
break;
case ControllerType::Right:
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, true);
if (result != ErrorCode::Success) {
return result;
}
break;
case Joycon::ControllerType::Pro:
result = GetLeftJoyStickCalibration(joycon_handle, calibration_data.left_stick, true);
if (result != ErrorCode::Success) {
return result;
}
result = GetRightJoyStickCalibration(joycon_handle, calibration_data.right_stick, true);
if (result != ErrorCode::Success) {
return result;
}
break;
default:
return ErrorCode::UnsupportedControllerType;
}
result = GetImuCalibration(joycon_handle, calibration_data.imu, true);
if (result != ErrorCode::Success) {
return result;
}
return ErrorCode::Success;
}
ErrorCode EnableImu(JoyconHandle& joycon_handle, bool enable) {
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
std::vector<u8> output;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result =
SendSubCommand(joycon_handle, SubCommand::ENABLE_IMU, buffer, buffer.size(), output);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return result;
}
ErrorCode EnableRumble(JoyconHandle& joycon_handle, bool enable) {
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
std::vector<u8> output;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const auto result =
SendSubCommand(joycon_handle, SubCommand::ENABLE_VIBRATION, buffer, buffer.size(), output);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return result;
}
ErrorCode SendData(JoyconHandle& joycon_handle, std::span<const u8> buffer, std::size_t size) {
const auto result = SDL_hid_write(joycon_handle.handle, buffer.data(), size);
if (result == -1) {
return ErrorCode::ErrorWritingData;
}
return ErrorCode::Success;
}
ErrorCode GetResponse(JoyconHandle& joycon_handle, SubCommand sc, std::vector<u8>& output) {
constexpr int timeout_mili = 100;
constexpr int max_tries = 10;
int tries = 0;
std::vector<u8> buffer(max_resp_size);
do {
int result =
SDL_hid_read_timeout(joycon_handle.handle, buffer.data(), max_resp_size, timeout_mili);
if (result < 1) {
LOG_ERROR(Input, "No response from joycon");
}
tries++;
} while (tries < max_tries && buffer[0] != 0x21 && buffer[14] != static_cast<u8>(sc));
if (tries >= max_tries) {
return ErrorCode::Timeout;
}
if (buffer[0] != 0x21 && buffer[14] != static_cast<u8>(sc)) {
return ErrorCode::WrongReply;
}
output = buffer;
return ErrorCode::Success;
}
ErrorCode SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc, std::span<const u8> buffer,
std::size_t size, std::vector<u8>& output) {
std::vector<u8> local_buffer(size + 11);
local_buffer[0] = static_cast<u8>(OutputReport::RUMBLE_AND_SUBCMD);
local_buffer[1] = GetCounter(joycon_handle);
for (std::size_t i = 0; i < 8; ++i) {
local_buffer[i + 2] = default_buffer[i];
}
local_buffer[10] = static_cast<u8>(sc);
for (std::size_t i = 0; i < size; ++i) {
local_buffer[11 + i] = buffer[i];
}
auto result = SendData(joycon_handle, local_buffer, size + 11);
if (result != ErrorCode::Success) {
return result;
}
result = GetResponse(joycon_handle, sc, output);
return ErrorCode::Success;
}
ErrorCode ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, std::vector<u8>& output, u8 size) {
constexpr std::size_t max_tries = 10;
std::size_t tries = 0;
std::vector<u8> buffer = {0x00, 0x00, 0x00, 0x00, size};
std::vector<u8> local_buffer(size + 20);
buffer[0] = static_cast<u8>(static_cast<u16>(addr) & 0x00FF);
buffer[1] = static_cast<u8>((static_cast<u16>(addr) & 0xFF00) >> 8);
do {
const auto result = SendSubCommand(joycon_handle, SubCommand::SPI_FLASH_READ, buffer,
buffer.size(), local_buffer);
if (result != ErrorCode::Success) {
return result;
}
tries++;
} while (tries < max_tries && (local_buffer[15] != buffer[0] || local_buffer[16] != buffer[1]));
if (tries >= max_tries) {
return ErrorCode::Timeout;
}
output = std::vector<u8>(local_buffer.begin() + 20, local_buffer.end());
return ErrorCode::Success;
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,425 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
// Based on dkms-hid-nintendo implementation and dekuNukem reverse engineering
// https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering
#pragma once
#include <span>
#include <vector>
#include <SDL_hidapi.h>
#include "common/common_types.h"
namespace InputCommon::Joycon {
constexpr u32 max_resp_size = 49;
constexpr std::array<u8, 8> default_buffer{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,
};
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,
RightSL = 0x001000,
RightSR = 0x002000,
R = 0x004000,
ZR = 0x008000,
Minus = 0x010000,
Plus = 0x020000,
StickR = 0x040000,
StickL = 0x080000,
Home = 0x100000,
Capture = 0x200000,
};
enum class OutputReport {
RUMBLE_AND_SUBCMD = 0x01,
FW_UPDATE_PKT = 0x03,
RUMBLE_ONLY = 0x10,
MCU_DATA = 0x11,
USB_CMD = 0x80,
};
enum class InputReport {
BUTTON_EVENT = 0x3F,
SUBCMD_REPLY = 0x21,
IMU_DATA = 0x30,
MCU_DATA = 0x31,
INPUT_USB_RESPONSE = 0x81,
};
enum class FeatureReport {
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 {
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,
};
enum class UsbSubCommand {
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 {
USR_MAGIC_0 = 0xB2,
USR_MAGIC_1 = 0xA1,
USRR_MAGI_SIZE = 2,
};
enum class CalAddr {
USER_LEFT_MAGIC = 0X8010,
USER_LEFT_DATA = 0X8012,
USER_RIGHT_MAGIC = 0X801B,
USER_RIGHT_DATA = 0X801D,
USER_IMU_MAGIC = 0X8026,
USER_IMU_DATA = 0X8028,
SERIAL_NUMBER = 0X6000,
DEVICE_TYPE = 0X6012,
COLOR_EXIST = 0X601B,
FACT_LEFT_DATA = 0X603d,
FACT_RIGHT_DATA = 0X6046,
COLOR_DATA = 0X6050,
FACT_IMU_DATA = 0X6020,
};
enum class ReportMode {
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 {
DPS250,
DPS500,
DPS1000,
DPS2000, // Default
};
enum class AccelerometerSensitivity {
G8, // Default
G4,
G2,
G16,
};
enum class GyroPerformance {
HZ833,
HZ208, // Default
};
enum class AccelerometerPerformance {
HZ200,
HZ100, // Default
};
enum class FirmwareVersion {
Rev_0,
};
enum class ErrorCode {
Success,
WrongReply,
Timeout,
UnsupportedControllerType,
HandleInUse,
ErrorReadingData,
ErrorWritingData,
Unknown,
};
struct ImuSensorCalibration {
s16 offset;
s16 scale;
};
struct ImuCalibration {
std::array<ImuSensorCalibration, 3> accelerometer;
std::array<ImuSensorCalibration, 3> gyro;
};
struct JoyStickAxisCalibration {
u16 max;
u16 min;
u16 center;
};
struct JoyStickCalibration {
JoyStickAxisCalibration x;
JoyStickAxisCalibration y;
};
struct CalibrationData {
JoyStickCalibration left_stick;
JoyStickCalibration right_stick;
ImuCalibration imu;
};
struct Color {
u32 body;
u32 buttons;
u32 left_grip;
u32 right_grip;
};
struct VibrationValue {
f32 low_amplitude;
f32 low_frequency;
f32 high_amplitude;
f32 high_frequency;
};
struct JoyconHandle {
SDL_hid_device* handle;
u8 packet_counter;
};
/**
* Increments and returns the packet counter of the handle
* @param joycon_handle device to send the data
* @returns packet counter value
*/
u8 GetCounter(JoyconHandle& joycon_handle);
/**
* Verifies and sets the joycon_handle if device is valid
* @param joycon_handle device to send the data
* @param device info from the driver
* @returns true if the device is valid
*/
ErrorCode CheckDeviceAccess(JoyconHandle& joycon_handle, SDL_hid_device_info* device);
/**
* Encondes the amplitude to be sended on a packet
* @param amplification original amplification value
* @returns encoded amplification value
*/
f32 EncodeRumbleAmplification(f32 amplification);
/**
* Sends a request to set the vibration of the joycon
* @param joycon_handle device to send the data
* @param vibration amplitude and frequency of the vibration
*/
ErrorCode SetVibration(JoyconHandle& joycon_handle, VibrationValue vibration);
/**
* Sends a request to set the configuration of the motion sensor
* @param joycon_handle device to send the data
* @param gsen gyro sensitivity
* @param gfrec gyro update frequency
* @param asen accelerometer sensitivity
* @param afrec accelerometer update frequency
*/
ErrorCode SetImuConfig(JoyconHandle& joycon_handle, GyroSensitivity gsen, GyroPerformance gfrec,
AccelerometerSensitivity asen, AccelerometerPerformance afrec);
/**
* Sends a request to set the polling mode of the joycon
* @param joycon_handle device to send the data
* @param report_mode polling mode to be set
*/
ErrorCode SetReportMode(JoyconHandle& joycon_handle, Joycon::ReportMode report_mode);
/**
* Sends a request to set a specific led pattern
* @param joycon_handle device to send the data
* @param leds led pattern to be set
*/
ErrorCode SetLedConfig(JoyconHandle& joycon_handle, u8 leds);
/**
* Sends a request to obtain the joycon colors from memory
* @param joycon_handle device to read the data
* @returns color object with the colors of the joycon
*/
ErrorCode GetColor(JoyconHandle& joycon_handle, Color& color);
/**
* Sends a request to obtain the joycon mac address from handle
* @param joycon_handle device to read the data
* @returns array containing the mac address
*/
ErrorCode GetMacAddress(JoyconHandle& joycon_handle, MacAddress& mac_address);
/**
* Sends a request to obtain the joycon serial number from memory
* @param joycon_handle device to read the data
* @returns array containing the serial number
*/
ErrorCode GetSerialNumber(JoyconHandle& joycon_handle, SerialNumber& serial_number);
/**
* Sends a request to obtain the joycon type from handle
* @param joycon_handle device to read the data
* @returns controller type of the joycon
*/
ErrorCode GetDeviceType(JoyconHandle& joycon_handle, ControllerType& controller_type);
/**
* Sends a request to obtain the joycon tyoe from memory
* @param joycon_handle device to read the data
* @returns controller type of the joycon
*/
ErrorCode GetDeviceType(SDL_hid_device_info* device_info, ControllerType& controller_type);
/**
* Sends a request to obtain the joycon firmware version
* @param joycon_handle device to read the data
* @returns u16 with the version number
*/
ErrorCode GetVersionNumber(JoyconHandle& joycon_handle, FirmwareVersion& version);
/**
* Sends a request to obtain the left stick calibration from memory
* @param joycon_handle device to read the data
* @param is_factory_calibration if true factory values will be returned
* @returns JoyStickCalibration of the left joystick
*/
ErrorCode GetLeftJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
bool is_factory_calibration);
/**
* Sends a request to obtain the right stick calibration from memory
* @param joycon_handle device to read the data
* @param is_factory_calibration if true factory values will be returned
* @returns JoyStickCalibration of the left joystick
*/
ErrorCode GetRightJoyStickCalibration(JoyconHandle& joycon_handle, JoyStickCalibration& calibration,
bool is_factory_calibration);
/**
* Sends a request to obtain the motion calibration from memory
* @param joycon_handle device to read the data
* @param is_factory_calibration if true factory values will be returned
* @returns ImuCalibration of the joystick motion
*/
ErrorCode GetImuCalibration(JoyconHandle& joycon_handle, ImuCalibration& calibration,
bool is_factory_calibration);
/**
* Requests user calibration from the joystick
* @param joycon_handle device to read the data
* @param controller_type type of calibration to be requested
* @returns User CalibrationData of the joystick
*/
ErrorCode GetUserCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
ControllerType controller_type);
/**
* Requests factory calibration from the joystick
* @param joycon_handle device to read the data
* @param controller_type type of calibration to be requested
* @returns Factory CalibrationData of the joystick
*/
ErrorCode GetFactoryCalibrationData(JoyconHandle& joycon_handle, CalibrationData& calibration_data,
ControllerType controller_type);
/**
* Sends a request to enable motion
* @param joycon_handle device to read the data
* @param is_factory_calibration if true motion data will be enabled
*/
ErrorCode EnableImu(JoyconHandle& joycon_handle, bool enable);
/**
* Sends a request to enable vibrations
* @param joycon_handle device to read the data
* @param is_factory_calibration if true rumble will be enabled
*/
ErrorCode EnableRumble(JoyconHandle& joycon_handle, bool enable);
/**
* Sends data to the joycon device
* @param joycon_handle device to send the data
* @param buffer data to be send
* @param size size in bytes of the buffer
*/
ErrorCode SendData(JoyconHandle& joycon_handle, std::span<const u8> buffer, std::size_t size);
/**
* Waits for incoming data of the joycon device that matchs the subcommand
* @param joycon_handle device to read the data
* @param sub_command type of data to be returned
* @returns a buffer containing the responce
*/
ErrorCode GetResponse(JoyconHandle& joycon_handle, SubCommand sub_command, std::vector<u8>& output);
/**
* Sends data to the joycon device
* @param joycon_handle device to send the data
* @param buffer data to be send
* @param size size in bytes of the buffer
*/
ErrorCode SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc, std::span<const u8> buffer,
std::size_t size, std::vector<u8>& output);
/**
* Sends data to the joycon device
* @param joycon_handle device to send the data
* @param buffer data to be send
* @param size size in bytes of the buffer
*/
ErrorCode ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, std::vector<u8>& output, u8 size);
} // namespace InputCommon::Joycon

View File

@@ -18,6 +18,7 @@
#include "input_common/input_poller.h"
#include "input_common/main.h"
#ifdef HAVE_SDL2
#include "input_common/drivers/joycon.h"
#include "input_common/drivers/sdl_driver.h"
#endif
@@ -87,6 +88,15 @@ struct InputSubsystem::Impl {
sdl_input_factory);
Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(),
sdl_output_factory);
joycon = std::make_shared<Joycons>("joycon");
joycon->SetMappingCallback(mapping_callback);
joycon_input_factory = std::make_shared<InputFactory>(joycon);
joycon_output_factory = std::make_shared<OutputFactory>(joycon);
Common::Input::RegisterFactory<Common::Input::InputDevice>(joycon->GetEngineName(),
joycon_input_factory);
Common::Input::RegisterFactory<Common::Input::OutputDevice>(joycon->GetEngineName(),
joycon_output_factory);
#endif
Common::Input::RegisterFactory<Common::Input::InputDevice>(
@@ -123,6 +133,10 @@ struct InputSubsystem::Impl {
Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName());
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName());
sdl.reset();
Common::Input::UnregisterFactory<Common::Input::InputDevice>(joycon->GetEngineName());
Common::Input::UnregisterFactory<Common::Input::OutputDevice>(joycon->GetEngineName());
joycon.reset();
#endif
Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button");
@@ -135,14 +149,17 @@ struct InputSubsystem::Impl {
};
auto keyboard_devices = keyboard->GetInputDevices();
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
auto mouse_devices = mouse->GetInputDevices();
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
auto gcadapter_devices = gcadapter->GetInputDevices();
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
auto udp_devices = udp_client->GetInputDevices();
devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end());
devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end());
devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end());
devices.insert(devices.end(), udp_devices.begin(), udp_devices.end());
#ifdef HAVE_SDL2
auto joycon_devices = joycon->GetInputDevices();
devices.insert(devices.end(), joycon_devices.begin(), joycon_devices.end());
auto sdl_devices = sdl->GetInputDevices();
devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end());
#endif
@@ -172,6 +189,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return sdl->GetAnalogMappingForDevice(params);
}
if (engine == joycon->GetEngineName()) {
return joycon->GetAnalogMappingForDevice(params);
}
#endif
return {};
}
@@ -195,6 +215,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return sdl->GetButtonMappingForDevice(params);
}
if (engine == joycon->GetEngineName()) {
return joycon->GetButtonMappingForDevice(params);
}
#endif
return {};
}
@@ -212,6 +235,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return sdl->GetMotionMappingForDevice(params);
}
if (engine == joycon->GetEngineName()) {
return joycon->GetMotionMappingForDevice(params);
}
#endif
return {};
}
@@ -237,6 +263,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return sdl->GetUIName(params);
}
if (engine == joycon->GetEngineName()) {
return joycon->GetUIName(params);
}
#endif
return Common::Input::ButtonNames::Invalid;
}
@@ -281,6 +310,9 @@ struct InputSubsystem::Impl {
if (engine == sdl->GetEngineName()) {
return true;
}
if (engine == joycon->GetEngineName()) {
return true;
}
#endif
return false;
}
@@ -292,6 +324,7 @@ struct InputSubsystem::Impl {
udp_client->BeginConfiguration();
#ifdef HAVE_SDL2
sdl->BeginConfiguration();
joycon->BeginConfiguration();
#endif
}
@@ -302,6 +335,7 @@ struct InputSubsystem::Impl {
udp_client->EndConfiguration();
#ifdef HAVE_SDL2
sdl->EndConfiguration();
joycon->EndConfiguration();
#endif
}
@@ -335,6 +369,9 @@ struct InputSubsystem::Impl {
std::shared_ptr<SDLDriver> sdl;
std::shared_ptr<InputFactory> sdl_input_factory;
std::shared_ptr<OutputFactory> sdl_output_factory;
std::shared_ptr<Joycons> joycon;
std::shared_ptr<InputFactory> joycon_input_factory;
std::shared_ptr<OutputFactory> joycon_output_factory;
#endif
};

View File

@@ -398,6 +398,7 @@ void Config::ReadControlValues() {
#else
Settings::values.enable_raw_input = false;
#endif
ReadBasicSetting(Settings::values.enable_sdl_joycons);
ReadBasicSetting(Settings::values.emulate_analog_keyboard);
Settings::values.mouse_panning = false;
ReadBasicSetting(Settings::values.mouse_panning_sensitivity);
@@ -1042,6 +1043,7 @@ void Config::SaveControlValues() {
WriteGlobalSetting(Settings::values.enable_accurate_vibrations);
WriteGlobalSetting(Settings::values.motion_enabled);
WriteBasicSetting(Settings::values.enable_raw_input);
WriteBasicSetting(Settings::values.enable_sdl_joycons);
WriteBasicSetting(Settings::values.keyboard_enabled);
WriteBasicSetting(Settings::values.emulate_analog_keyboard);
WriteBasicSetting(Settings::values.mouse_panning_sensitivity);

View File

@@ -136,6 +136,7 @@ void ConfigureInputAdvanced::ApplyConfiguration() {
Settings::values.enable_udp_controller = ui->enable_udp_controller->isChecked();
Settings::values.controller_navigation = ui->controller_navigation->isChecked();
Settings::values.enable_ring_controller = ui->enable_ring_controller->isChecked();
Settings::values.enable_sdl_joycons = ui->enable_sdl_joycons->isChecked();
}
void ConfigureInputAdvanced::LoadConfiguration() {
@@ -169,6 +170,7 @@ void ConfigureInputAdvanced::LoadConfiguration() {
ui->enable_udp_controller->setChecked(Settings::values.enable_udp_controller.GetValue());
ui->controller_navigation->setChecked(Settings::values.controller_navigation.GetValue());
ui->enable_ring_controller->setChecked(Settings::values.enable_ring_controller.GetValue());
ui->enable_sdl_joycons->setChecked(Settings::values.enable_sdl_joycons.GetValue());
UpdateUIEnabled();
}

View File

@@ -2682,6 +2682,22 @@
</widget>
</item>
<item row="5" column="0">
<widget class="QCheckBox" name="enable_sdl_joycons">
<property name="toolTip">
<string>Requires restarting yuzu</string>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>23</height>
</size>
</property>
<property name="text">
<string>Enable SDL Joycons</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QCheckBox" name="mouse_panning">
<property name="minimumSize">
<size>
@@ -2694,7 +2710,7 @@
</property>
</widget>
</item>
<item row="5" column="2">
<item row="7" column="2">
<widget class="QSpinBox" name="mouse_panning_sensitivity">
<property name="toolTip">
<string>Mouse sensitivity</string>
@@ -2716,14 +2732,14 @@
</property>
</widget>
</item>
<item row="6" column="0">
<item row="7" column="0">
<widget class="QLabel" name="motion_touch">
<property name="text">
<string>Motion / Touch</string>
</property>
</widget>
</item>
<item row="6" column="2">
<item row="7" column="2">
<widget class="QPushButton" name="buttonMotionTouch">
<property name="text">
<string>Configure</string>

View File

@@ -67,6 +67,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
return QObject::tr("R");
case Common::Input::ButtonNames::TriggerL:
return QObject::tr("L");
case Common::Input::ButtonNames::TriggerZR:
return QObject::tr("ZR");
case Common::Input::ButtonNames::TriggerZL:
return QObject::tr("ZL");
case Common::Input::ButtonNames::TriggerSR:
return QObject::tr("SR");
case Common::Input::ButtonNames::TriggerSL:
return QObject::tr("SL");
case Common::Input::ButtonNames::ButtonStickL:
return QObject::tr("Stick L");
case Common::Input::ButtonNames::ButtonStickR:
return QObject::tr("Stick R");
case Common::Input::ButtonNames::ButtonA:
return QObject::tr("A");
case Common::Input::ButtonNames::ButtonB:
@@ -77,6 +89,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
return QObject::tr("Y");
case Common::Input::ButtonNames::ButtonStart:
return QObject::tr("Start");
case Common::Input::ButtonNames::ButtonPlus:
return QObject::tr("Plus");
case Common::Input::ButtonNames::ButtonMinus:
return QObject::tr("Minus");
case Common::Input::ButtonNames::ButtonHome:
return QObject::tr("Home");
case Common::Input::ButtonNames::ButtonCapture:
return QObject::tr("Capture");
case Common::Input::ButtonNames::L1:
return QObject::tr("L1");
case Common::Input::ButtonNames::L2: