From d27a4b3e078c8e11bf76ef7e9e602cbc0de36780 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Wed, 1 Dec 2021 14:46:25 -0600 Subject: [PATCH] input_common: Implement custom joycon driver --- src/common/input.h | 20 +- src/input_common/CMakeLists.txt | 8 +- src/input_common/drivers/joycon.cpp | 583 ++++++++++++++++++ src/input_common/drivers/joycon.h | 126 ++++ src/input_common/helpers/joycon_protocol.cpp | 357 +++++++++++ src/input_common/helpers/joycon_protocol.h | 366 +++++++++++ src/input_common/main.cpp | 43 +- .../configuration/configure_input_player.cpp | 20 + 8 files changed, 1514 insertions(+), 9 deletions(-) create mode 100644 src/input_common/drivers/joycon.cpp create mode 100644 src/input_common/drivers/joycon.h create mode 100644 src/input_common/helpers/joycon_protocol.cpp create mode 100644 src/input_common/helpers/joycon_protocol.h diff --git a/src/common/input.h b/src/common/input.h index 54fcb24b04..515f8996bf 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -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 diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index d4fa69a775..50240da005 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -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) diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp new file mode 100644 index 0000000000..16d7c79f4d --- /dev/null +++ b/src/input_common/drivers/joycon.cpp @@ -0,0 +1,583 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include + +#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 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 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 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(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(raw_value - calibration.center); + if (value > 0.0f) { + return value / calibration.max; + } + return value / calibration.min; +} + +void Joycons::UpdateLeftPadInput(JoyconData& jc, std::span buffer) { + static constexpr std::array 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(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(left_buttons[i])) != 0; + const int button = static_cast(left_buttons[i]); + SetButton(identifier, button, button_status); + } + + const u16 raw_left_axis_x = static_cast(buffer[6] | ((buffer[7] & 0xf) << 8)); + const u16 raw_left_axis_y = static_cast((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(PadAxes::LeftStickX), left_axis_x); + SetAxis(identifier, static_cast(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(PadMotion::LeftMotion), left_motion); + } +} + +void Joycons::UpdateRightPadInput(JoyconData& jc, std::span buffer) { + static constexpr std::array 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((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(right_buttons[i])) != 0; + const int button = static_cast(right_buttons[i]); + SetButton(identifier, button, button_status); + } + + const u16 raw_right_axis_x = static_cast(buffer[9] | ((buffer[10] & 0xf) << 8)); + const u16 raw_right_axis_y = static_cast((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(PadAxes::RightStickX), right_axis_x); + SetAxis(identifier, static_cast(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(PadMotion::RightMotion), right_motion); + } +} + +void Joycons::UpdateProPadInput(JoyconData& jc, std::span buffer) { + static constexpr std::array 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(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(pro_buttons[i])) != 0; + const int button = static_cast(pro_buttons[i]); + SetButton(identifier, button, button_status); + } + + const u16 raw_left_axis_x = static_cast(buffer[6] | ((buffer[7] & 0xf) << 8)); + const u16 raw_left_axis_y = static_cast((buffer[7] >> 4) | (buffer[8] << 4)); + const u16 raw_right_axis_x = static_cast(buffer[9] | ((buffer[10] & 0xf) << 8)); + const u16 raw_right_axis_y = static_cast((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(PadAxes::LeftStickX), left_axis_x); + SetAxis(identifier, static_cast(PadAxes::LeftStickY), left_axis_y); + SetAxis(identifier, static_cast(PadAxes::RightStickX), right_axis_x); + SetAxis(identifier, static_cast(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(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 buffer) const { + const size_t offset = (sensor * 6) + (axis * 2); + return static_cast(buffer[13 + offset] | (buffer[14 + offset] << 8)); +} + +BasicMotion Joycons::GetMotionInput(std::span buffer, Joycon::ImuCalibration calibration, + Joycon::AccelerometerSensitivity accel_sensitivity, + Joycon::GyroSensitivity gyro_sensitivity) const { + std::array 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 Joycons::GetInputDevices() const { + std::vector 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, 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(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(PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast(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(PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast(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(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 diff --git a/src/input_common/drivers/joycon.h b/src/input_common/drivers/joycon.h new file mode 100644 index 0000000000..30893e0f3b --- /dev/null +++ b/src/input_common/drivers/joycon.h @@ -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 +#include + +#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 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 mac; + Joycon::CalibrationData calibration; + Joycon::ControllerType type; + std::array 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 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 buffer); + void UpdateLeftPadInput(JoyconData& jc, std::span buffer); + void UpdateRightPadInput(JoyconData& jc, std::span buffer); + void UpdateProPadInput(JoyconData& jc, std::span 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 buffer) const; + BasicMotion GetMotionInput(std::span 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 joycon_data{}; +}; + +} // namespace InputCommon diff --git a/src/input_common/helpers/joycon_protocol.cpp b/src/input_common/helpers/joycon_protocol.cpp new file mode 100644 index 0000000000..48d121b099 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol.cpp @@ -0,0 +1,357 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include + +#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 buffer(Joycon::max_resp_size); + + buffer[0] = static_cast(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(roundf(128 * log2f(vibration.high_frequency * 0.1f)) - 0x180); + const u8 encoded_lf = + static_cast(roundf(32 * log2f(vibration.low_frequency * 0.1f)) - 0x40); + const u8 encoded_hamp = static_cast(EncodeRumbleAmplification(vibration.high_amplitude)); + const u8 encoded_lamp = static_cast(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(encoded_hf & 0xff); + buffer[3 + offset] = static_cast((encoded_hf >> 8) & 0xff); + buffer[4 + offset] = static_cast(encoded_lf & 0xff); + + buffer[3 + offset] |= static_cast(amplitude); + buffer[4 + offset] |= static_cast(parity); + buffer[5 + offset] = static_cast(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 buffer{static_cast(gsen), static_cast(asen), + static_cast(gfrec), static_cast(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 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 buffer{static_cast(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 buffer; + SDL_hid_set_nonblocking(joycon_handle.handle, 0); + buffer = ReadSPI(joycon_handle, Joycon::CalAddr::COLOR_DATA, 12); + joycon_color.body = static_cast((buffer[2] << 16) | (buffer[1] << 8) | buffer[0]); + joycon_color.buttons = static_cast((buffer[5] << 16) | (buffer[4] << 8) | buffer[3]); + joycon_color.left_grip = static_cast((buffer[8] << 16) | (buffer[7] << 8) | buffer[6]); + joycon_color.right_grip = static_cast((buffer[11] << 16) | (buffer[10] << 8) | buffer[9]); + SDL_hid_set_nonblocking(joycon_handle.handle, 1); + return joycon_color; +} + +std::array GetMacAddress(JoyconHandle& joycon_handle) { + std::array 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(std::stoi(value, 0, 16)); + } + return mac_address; +} + +std::array GetSerialNumber(JoyconHandle& joycon_handle) { + std::array serial_number; + SDL_hid_set_nonblocking(joycon_handle.handle, 0); + const std::vector 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 buffer = ReadSPI(joycon_handle, Joycon::CalAddr::DEVICE_TYPE, 1); + SDL_hid_set_nonblocking(joycon_handle.handle, 1); + return static_cast(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 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(((buffer[1] & 0x0F) << 8) | buffer[0]); + joystick.y.max = static_cast((buffer[2] << 4) | (buffer[1] >> 4)); + joystick.x.center = static_cast(((buffer[4] & 0x0F) << 8) | buffer[3]); + joystick.y.center = static_cast((buffer[5] << 4) | (buffer[4] >> 4)); + joystick.x.min = static_cast(((buffer[7] & 0x0F) << 8) | buffer[6]); + joystick.y.min = static_cast((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 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(((buffer[1] & 0x0F) << 8) | buffer[0]); + joystick.y.center = static_cast((buffer[2] << 4) | (buffer[1] >> 4)); + joystick.x.min = static_cast(((buffer[4] & 0x0F) << 8) | buffer[3]); + joystick.y.min = static_cast((buffer[5] << 4) | (buffer[4] >> 4)); + joystick.x.max = static_cast(((buffer[7] & 0x0F) << 8) | buffer[6]); + joystick.y.max = static_cast((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 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(buffer[index] | (buffer[index + 1] << 8)); + imu.accelerometer[i].scale = static_cast(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(buffer[index + 12] | (buffer[index + 13] << 8)); + imu.gyro[i].scale = static_cast(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 buffer{static_cast(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 buffer{static_cast(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 buffer, std::size_t size) { + SDL_hid_write(joycon_handle.handle, buffer.data(), size); +} + +std::vector GetResponse(JoyconHandle& joycon_handle, SubCommand sc) { + int tries = 0; + std::vector 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(sc)); + return buffer; +} + +std::vector SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc, + std::span buffer, std::size_t size) { + std::vector local_buffer(size + 11); + + local_buffer[0] = static_cast(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(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 ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, u8 size) { + std::vector buffer = {0x00, 0x00, 0x00, 0x00, size}; + std::vector local_buffer(size + 20); + + buffer[0] = static_cast(static_cast(addr) & 0x00FF); + buffer[1] = static_cast((static_cast(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(local_buffer.begin() + 20, local_buffer.end()); +} + +} // namespace InputCommon::Joycon diff --git a/src/input_common/helpers/joycon_protocol.h b/src/input_common/helpers/joycon_protocol.h new file mode 100644 index 0000000000..6883bf8352 --- /dev/null +++ b/src/input_common/helpers/joycon_protocol.h @@ -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 +#include +#include + +#include "common/common_types.h" + +namespace InputCommon::Joycon { +constexpr u32 max_resp_size = 49; +constexpr std::array 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 accelerometer; + std::array 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 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 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 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 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 SendSubCommand(JoyconHandle& joycon_handle, SubCommand sc, + std::span 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 ReadSPI(JoyconHandle& joycon_handle, CalAddr addr, u8 size); + +} // namespace InputCommon::Joycon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 21834fb6b3..9b944ed2e7 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -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(sdl->GetEngineName(), sdl_output_factory); + + joycon = std::make_shared("joycon"); + joycon->SetMappingCallback(mapping_callback); + joycon_input_factory = std::make_shared(joycon); + joycon_output_factory = std::make_shared(joycon); + Common::Input::RegisterFactory(joycon->GetEngineName(), + joycon_input_factory); + Common::Input::RegisterFactory(joycon->GetEngineName(), + joycon_output_factory); #endif Common::Input::RegisterFactory( @@ -123,6 +133,10 @@ struct InputSubsystem::Impl { Common::Input::UnregisterFactory(sdl->GetEngineName()); Common::Input::UnregisterFactory(sdl->GetEngineName()); sdl.reset(); + + Common::Input::UnregisterFactory(joycon->GetEngineName()); + Common::Input::UnregisterFactory(joycon->GetEngineName()); + joycon.reset(); #endif Common::Input::UnregisterFactory("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 sdl; std::shared_ptr sdl_input_factory; std::shared_ptr sdl_output_factory; + std::shared_ptr joycon; + std::shared_ptr joycon_input_factory; + std::shared_ptr joycon_output_factory; #endif }; diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index b4f5d172aa..bf3b2d2fd9 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp @@ -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: