input_common: Implement custom joycon driver

This commit is contained in:
Narr the Reg
2021-12-01 14:46:25 -06:00
committed by german77
parent 7f77aafe41
commit d27a4b3e07
8 changed files with 1514 additions and 9 deletions

View File

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

View File

@@ -5,8 +5,6 @@ add_library(input_common STATIC
drivers/keyboard.h drivers/keyboard.h
drivers/mouse.cpp drivers/mouse.cpp
drivers/mouse.h drivers/mouse.h
drivers/sdl_driver.cpp
drivers/sdl_driver.h
drivers/tas_input.cpp drivers/tas_input.cpp
drivers/tas_input.h drivers/tas_input.h
drivers/touch_screen.cpp drivers/touch_screen.cpp
@@ -53,11 +51,15 @@ endif()
if (ENABLE_SDL2) if (ENABLE_SDL2)
target_sources(input_common PRIVATE target_sources(input_common PRIVATE
drivers/joycon.cpp
drivers/joycon.h
drivers/sdl_driver.cpp drivers/sdl_driver.cpp
drivers/sdl_driver.h 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_compile_definitions(input_common PRIVATE HAVE_SDL2)
target_link_libraries(input_common PRIVATE SDL2)
endif() endif()
target_link_libraries(input_common PRIVATE usb) target_link_libraries(input_common PRIVATE usb)

View File

@@ -0,0 +1,583 @@
// 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_input.h"
#include "common/thread.h"
#include "input_common/drivers/joycon.h"
namespace InputCommon {
Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) {
LOG_INFO(Input, "JC Adapter 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) {
if (Joycon::GetDeviceType(cur_dev) == joycon_data[i].type) {
LOG_WARNING(Input, "Device already exist");
skip_device = true;
break;
}
}
if (!skip_device) {
if (Joycon::CheckDeviceAccess(joycon_data[port].joycon_handle, cur_dev)) {
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.type = Joycon::GetDeviceType(handle);
// joycon_data[port].mac= Joycon::GetMac();
// SetSerialNumber(joycon_data[port]);
// SetVersionNumber(joycon_data[port]);
// SetDeviceType(joycon_data[port]);
joycon.calibration = Joycon::GetFactoryCalibrationData(handle, 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::INVALID_UUID},
.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

@@ -0,0 +1,357 @@
// 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;
}
bool CheckDeviceAccess(JoyconHandle& joycon_handle, SDL_hid_device_info* device_info) {
if (GetDeviceType(device_info) == ControllerType::None) {
return false;
}
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 false;
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return true;
}
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);
}
void SetVibration(JoyconHandle& joycon_handle, VibrationValue vibration) {
std::vector<u8> buffer(Joycon::max_resp_size);
buffer[0] = static_cast<u8>(Joycon::Output::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];
}
SendData(joycon_handle, buffer, max_resp_size);
return;
}
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);
}
SendData(joycon_handle, buffer, max_resp_size);
}
void 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)};
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
SendSubCommand(joycon_handle, SubCommand::SET_IMU_SENSITIVITY, buffer, 4);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
}
void SetLedConfig(JoyconHandle& joycon_handle, u8 leds) {
const std::vector<u8> buffer{leds};
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
SendSubCommand(joycon_handle, SubCommand::SET_PLAYER_LIGHTS, buffer, 1);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
}
void SetReportMode(JoyconHandle& joycon_handle, ReportMode report_mode) {
const std::vector<u8> buffer{static_cast<u8>(report_mode)};
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
SendSubCommand(joycon_handle, SubCommand::SET_REPORT_MODE, buffer, 1);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
}
Color GetColor(JoyconHandle& joycon_handle) {
Color joycon_color;
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
buffer = ReadSPI(joycon_handle, Joycon::CalAddr::COLOR_DATA, 12);
joycon_color.body = static_cast<u32>((buffer[2] << 16) | (buffer[1] << 8) | buffer[0]);
joycon_color.buttons = static_cast<u32>((buffer[5] << 16) | (buffer[4] << 8) | buffer[3]);
joycon_color.left_grip = static_cast<u32>((buffer[8] << 16) | (buffer[7] << 8) | buffer[6]);
joycon_color.right_grip = static_cast<u32>((buffer[11] << 16) | (buffer[10] << 8) | buffer[9]);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return joycon_color;
}
std::array<u8, 6> GetMacAddress(JoyconHandle& joycon_handle) {
std::array<u8, 6> 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");
return mac_address;
}
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 mac_address;
}
std::array<u8, 15> GetSerialNumber(JoyconHandle& joycon_handle) {
std::array<u8, 15> serial_number;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const std::vector<u8> buffer = ReadSPI(joycon_handle, Joycon::CalAddr::SERIAL_NUMBER, 16);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
for (std::size_t i = 0; i < serial_number.size(); ++i) {
serial_number[i] = buffer[i + 1];
}
return serial_number;
}
ControllerType GetDeviceType(SDL_hid_device_info* device_info) {
if (device_info->vendor_id != 0x057e) {
return ControllerType::None;
}
switch (device_info->product_id) {
case 0x2006:
return ControllerType::Left;
case 0x2007:
return ControllerType::Right;
case 0x2009:
return ControllerType::Pro;
default:
return ControllerType::None;
}
}
ControllerType GetDeviceType(JoyconHandle& joycon_handle) {
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const std::vector<u8> buffer = ReadSPI(joycon_handle, Joycon::CalAddr::DEVICE_TYPE, 1);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
return static_cast<Joycon::ControllerType>(buffer[0]);
}
u16 GetVersionNumber([[maybe_unused]] JoyconHandle& joycon_handle) {
// Not implemented
return 0;
}
JoyStickCalibration GetLeftJoyStickCalibration(JoyconHandle& joycon_handle,
bool is_factory_calibration) {
JoyStickCalibration joystick{};
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
if (is_factory_calibration) {
buffer = ReadSPI(joycon_handle, CalAddr::FACT_LEFT_DATA, 9);
} else {
buffer = ReadSPI(joycon_handle, CalAddr::USER_LEFT_DATA, 9);
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
joystick.x.max = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
joystick.y.max = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
joystick.x.center = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
joystick.y.center = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
joystick.x.min = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
joystick.y.min = static_cast<u16>((buffer[8] << 4) | (buffer[7] >> 4));
// Nintendo fix for drifting stick
// buffer = ReadSPI(0x60, 0x86 , 16);
// joystick.deadzone = (u16)((buffer[4] << 8) & 0xF00 | buffer[3]);
return joystick;
}
JoyStickCalibration GetRightJoyStickCalibration(JoyconHandle& joycon_handle,
bool is_factory_calibration) {
JoyStickCalibration joystick{};
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
if (is_factory_calibration) {
buffer = ReadSPI(joycon_handle, CalAddr::FACT_RIGHT_DATA, 9);
} else {
buffer = ReadSPI(joycon_handle, CalAddr::USER_RIGHT_DATA, 9);
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
joystick.x.center = static_cast<u16>(((buffer[1] & 0x0F) << 8) | buffer[0]);
joystick.y.center = static_cast<u16>((buffer[2] << 4) | (buffer[1] >> 4));
joystick.x.min = static_cast<u16>(((buffer[4] & 0x0F) << 8) | buffer[3]);
joystick.y.min = static_cast<u16>((buffer[5] << 4) | (buffer[4] >> 4));
joystick.x.max = static_cast<u16>(((buffer[7] & 0x0F) << 8) | buffer[6]);
joystick.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 joystick;
}
ImuCalibration GetImuCalibration(JoyconHandle& joycon_handle, bool is_factory_calibration) {
ImuCalibration imu{};
std::vector<u8> buffer;
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
if (is_factory_calibration) {
buffer = ReadSPI(joycon_handle, CalAddr::FACT_IMU_DATA, 24);
} else {
buffer = ReadSPI(joycon_handle, CalAddr::USER_IMU_DATA, 24);
}
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
for (std::size_t i = 0; i < imu.accelerometer.size(); ++i) {
const std::size_t index = i * 2;
imu.accelerometer[i].offset = static_cast<s16>(buffer[index] | (buffer[index + 1] << 8));
imu.accelerometer[i].scale = static_cast<s16>(buffer[index + 6] | (buffer[index + 7] << 8));
}
for (std::size_t i = 0; i < imu.gyro.size(); ++i) {
const std::size_t index = i * 2;
imu.gyro[i].offset = static_cast<s16>(buffer[index + 12] | (buffer[index + 13] << 8));
imu.gyro[i].scale = static_cast<s16>(buffer[index + 18] | (buffer[index + 19] << 8));
}
return imu;
}
CalibrationData GetUserCalibrationData(JoyconHandle& joycon_handle,
ControllerType controller_type) {
CalibrationData calibration{};
switch (controller_type) {
case ControllerType::Left:
calibration.left_stick = GetLeftJoyStickCalibration(joycon_handle, false);
break;
case ControllerType::Right:
calibration.right_stick = GetRightJoyStickCalibration(joycon_handle, false);
break;
case Joycon::ControllerType::Pro:
calibration.left_stick = GetLeftJoyStickCalibration(joycon_handle, false);
calibration.right_stick = GetRightJoyStickCalibration(joycon_handle, false);
break;
default:
break;
}
calibration.imu = GetImuCalibration(joycon_handle, false);
return calibration;
}
CalibrationData GetFactoryCalibrationData(JoyconHandle& joycon_handle,
ControllerType controller_type) {
CalibrationData calibration{};
switch (controller_type) {
case ControllerType::Left:
calibration.left_stick = GetLeftJoyStickCalibration(joycon_handle, true);
break;
case ControllerType::Right:
calibration.right_stick = GetRightJoyStickCalibration(joycon_handle, true);
break;
case Joycon::ControllerType::Pro:
calibration.left_stick = GetLeftJoyStickCalibration(joycon_handle, true);
calibration.right_stick = GetRightJoyStickCalibration(joycon_handle, true);
break;
default:
break;
}
calibration.imu = GetImuCalibration(joycon_handle, true);
return calibration;
}
void EnableImu(JoyconHandle& joycon_handle, bool enable) {
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
SendSubCommand(joycon_handle, SubCommand::ENABLE_IMU, buffer, 1);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
}
void EnableRumble(JoyconHandle& joycon_handle, bool enable) {
SDL_hid_set_nonblocking(joycon_handle.handle, 0);
const std::vector<u8> buffer{static_cast<u8>(enable ? 1 : 0)};
SendSubCommand(joycon_handle, SubCommand::ENABLE_VIBRATION, buffer, 1);
SDL_hid_set_nonblocking(joycon_handle.handle, 1);
}
void SendData(JoyconHandle& joycon_handle, std::span<const u8> buffer, std::size_t size) {
SDL_hid_write(joycon_handle.handle, buffer.data(), size);
}
std::vector<u8> GetResponse(JoyconHandle& joycon_handle, SubCommand sc) {
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, 100);
if (result < 1) {
LOG_ERROR(Input, "No response from joycon");
}
tries++;
} while (tries < 10 && buffer[0] != 0x21 && buffer[14] != static_cast<u8>(sc));
return buffer;
}
std::vector<u8> SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc,
std::span<const u8> buffer, std::size_t size) {
std::vector<u8> local_buffer(size + 11);
local_buffer[0] = static_cast<u8>(Output::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];
}
SendData(joycon_handle, local_buffer, size + 11);
return GetResponse(joycon_handle, sc);
}
std::vector<u8> ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, u8 size) {
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);
for (std::size_t i = 0; i < 100; ++i) {
local_buffer = SendSubCommand(joycon_handle, SubCommand::SPI_FLASH_READ, buffer, 5);
if (local_buffer[15] == buffer[0] && local_buffer[16] == buffer[1]) {
break;
}
}
return std::vector<u8>(local_buffer.begin() + 20, local_buffer.end());
}
} // namespace InputCommon::Joycon

View File

@@ -0,0 +1,366 @@
// Copyright 2021 yuzu Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included
#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};
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 Output {
RUMBLE_AND_SUBCMD = 0x01,
FW_UPDATE_PKT = 0x03,
RUMBLE_ONLY = 0x10,
MCU_DATA = 0x11,
USB_CMD = 0x80,
};
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 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,
};
enum class AccelerometerSensitivity {
G8,
G4,
G2,
G16,
};
enum class GyroPerformance {
HZ833,
HZ208,
};
enum class AccelerometerPerformance {
HZ200,
HZ100,
};
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 device info from the driver
* @returns true if the device is valid
*/
bool CheckDeviceAccess(JoyconHandle& joycon_handle, SDL_hid_device_info* device);
/**
* 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
*/
void SetImuConfig(JoyconHandle& joycon_handle, GyroSensitivity gsen, GyroPerformance gfrec,
AccelerometerSensitivity asen, AccelerometerPerformance afrec);
/**
* 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
*/
void SetVibration(JoyconHandle& joycon_handle, VibrationValue vibration);
/**
* 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
*/
void 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
*/
void 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
*/
Color GetColor(JoyconHandle& joycon_handle);
/**
* 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
*/
std::array<u8, 6> GetMacAddress(JoyconHandle& joycon_handle);
/**
* 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
*/
std::array<u8, 15> GetSerialNumber(JoyconHandle& joycon_handle);
/**
* Sends a request to obtain the joycon type from handle
* @param joycon_handle device to read the data
* @returns controller type of the joycon
*/
ControllerType GetDeviceType(JoyconHandle& joycon_handle);
/**
* Sends a request to obtain the joycon tyoe from memory
* @param joycon_handle device to read the data
* @returns controller type of the joycon
*/
ControllerType GetDeviceType(SDL_hid_device_info* device_info);
/**
* Sends a request to obtain the joycon firmware version
* @param joycon_handle device to read the data
* @returns u16 with the version number
*/
u16 GetVersionNumber(JoyconHandle& joycon_handle);
/**
* 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
*/
JoyStickCalibration GetLeftJoyStickCalibration(JoyconHandle& joycon_handle,
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
*/
JoyStickCalibration GetRightJoyStickCalibration(JoyconHandle& joycon_handle,
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
*/
ImuCalibration GetImuCalibration(JoyconHandle& joycon_handle, 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
*/
CalibrationData GetUserCalibrationData(JoyconHandle& joycon_handle, 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
*/
CalibrationData GetFactoryCalibrationData(JoyconHandle& joycon_handle,
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
*/
void 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
*/
void 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
*/
void 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
*/
std::vector<u8> GetResponse(JoyconHandle& joycon_handle, SubCommand sub_command);
/**
* 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
*/
std::vector<u8> SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc,
std::span<const u8> buffer, std::size_t size);
/**
* 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
*/
std::vector<u8> ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, u8 size);
} // namespace InputCommon::Joycon

View File

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

View File

@@ -67,6 +67,18 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
return QObject::tr("R"); return QObject::tr("R");
case Common::Input::ButtonNames::TriggerL: case Common::Input::ButtonNames::TriggerL:
return QObject::tr("L"); 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: case Common::Input::ButtonNames::ButtonA:
return QObject::tr("A"); return QObject::tr("A");
case Common::Input::ButtonNames::ButtonB: case Common::Input::ButtonNames::ButtonB:
@@ -77,6 +89,14 @@ QString GetButtonName(Common::Input::ButtonNames button_name) {
return QObject::tr("Y"); return QObject::tr("Y");
case Common::Input::ButtonNames::ButtonStart: case Common::Input::ButtonNames::ButtonStart:
return QObject::tr("Start"); 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: case Common::Input::ButtonNames::L1:
return QObject::tr("L1"); return QObject::tr("L1");
case Common::Input::ButtonNames::L2: case Common::Input::ButtonNames::L2: