From d80e6c399bf8196646cca5ac1265d122638bb96b Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Tue, 20 Dec 2022 11:34:33 -0600 Subject: input_common: Initial skeleton for custom joycon driver --- src/input_common/drivers/joycon.cpp | 615 ++++++++++++++++++++++++++++++++++++ 1 file changed, 615 insertions(+) create mode 100644 src/input_common/drivers/joycon.cpp (limited to 'src/input_common/drivers/joycon.cpp') diff --git a/src/input_common/drivers/joycon.cpp b/src/input_common/drivers/joycon.cpp new file mode 100644 index 000000000..eab10d11c --- /dev/null +++ b/src/input_common/drivers/joycon.cpp @@ -0,0 +1,615 @@ +// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include + +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/joycon.h" +#include "input_common/helpers/joycon_driver.h" +#include "input_common/helpers/joycon_protocol/joycon_types.h" + +namespace InputCommon { + +Joycons::Joycons(const std::string& input_engine_) : InputEngine(input_engine_) { + LOG_INFO(Input, "Joycon driver Initialization started"); + const int init_res = SDL_hid_init(); + if (init_res == 0) { + Setup(); + } else { + LOG_ERROR(Input, "Hidapi could not be initialized. failed with error = {}", init_res); + } +} + +Joycons::~Joycons() { + Reset(); +} + +void Joycons::Reset() { + scan_thread = {}; + for (const auto& device : left_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + for (const auto& device : right_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + for (const auto& device : pro_joycons) { + if (!device) { + continue; + } + device->Stop(); + } + SDL_hid_exit(); +} + +void Joycons::Setup() { + u32 port = 0; + for (auto& device : left_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Left)); + device = std::make_shared(port++); + } + for (auto& device : right_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Right)); + device = std::make_shared(port++); + } + for (auto& device : pro_joycons) { + PreSetController(GetIdentifier(port, Joycon::ControllerType::Pro)); + device = std::make_shared(port++); + } + + if (!scan_thread_running) { + scan_thread = std::jthread([this](std::stop_token stop_token) { ScanThread(stop_token); }); + } +} + +void Joycons::ScanThread(std::stop_token stop_token) { + constexpr u16 nintendo_vendor_id = 0x057e; + Common::SetCurrentThreadName("yuzu:input:JoyconScanThread"); + scan_thread_running = true; + while (!stop_token.stop_requested()) { + SDL_hid_device_info* devs = SDL_hid_enumerate(nintendo_vendor_id, 0x0); + SDL_hid_device_info* cur_dev = devs; + + while (cur_dev) { + if (IsDeviceNew(cur_dev)) { + LOG_DEBUG(Input, "Device Found,type : {:04X} {:04X}", cur_dev->vendor_id, + cur_dev->product_id); + RegisterNewDevice(cur_dev); + } + cur_dev = cur_dev->next; + } + + std::this_thread::sleep_for(std::chrono::seconds(5)); + } + scan_thread_running = false; +} + +bool Joycons::IsDeviceNew(SDL_hid_device_info* device_info) const { + Joycon::ControllerType type{}; + Joycon::SerialNumber serial_number{}; + + const auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); + if (result != Joycon::DriverResult::Success) { + return false; + } + + const auto result2 = Joycon::JoyconDriver::GetSerialNumber(device_info, serial_number); + if (result2 != Joycon::DriverResult::Success) { + return false; + } + + auto is_handle_identical = [&](std::shared_ptr device) { + if (!device) { + return false; + } + if (!device->IsConnected()) { + return false; + } + if (device->GetHandleSerialNumber() != serial_number) { + return false; + } + return true; + }; + + // Check if device already exist + switch (type) { + case Joycon::ControllerType::Left: + for (const auto& device : left_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + case Joycon::ControllerType::Right: + for (const auto& device : right_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + case Joycon::ControllerType::Pro: + case Joycon::ControllerType::Grip: + for (const auto& device : pro_joycons) { + if (is_handle_identical(device)) { + return false; + } + } + break; + default: + return false; + } + + return true; +} + +void Joycons::RegisterNewDevice(SDL_hid_device_info* device_info) { + Joycon::ControllerType type{}; + auto result = Joycon::JoyconDriver::GetDeviceType(device_info, type); + auto handle = GetNextFreeHandle(type); + if (handle == nullptr) { + LOG_WARNING(Input, "No free handles available"); + return; + } + if (result == Joycon::DriverResult::Success) { + result = handle->RequestDeviceAccess(device_info); + } + if (result == Joycon::DriverResult::Success) { + LOG_WARNING(Input, "Initialize device"); + + std::function on_battery_data; + std::function on_button_data; + std::function on_stick_data; + std::function)> on_motion_data; + std::function on_ring_data; + std::function&)> on_amiibo_data; + + const std::size_t port = handle->GetDevicePort(); + handle->on_battery_data = { + [this, port, type](Joycon::Battery value) { OnBatteryUpdate(port, type, value); }}; + handle->on_color_data = { + [this, port, type](Joycon::Color value) { OnColorUpdate(port, type, value); }}; + handle->on_button_data = { + [this, port, type](int id, bool value) { OnButtonUpdate(port, type, id, value); }}; + handle->on_stick_data = { + [this, port, type](int id, f32 value) { OnStickUpdate(port, type, id, value); }}; + handle->on_motion_data = {[this, port, type](int id, Joycon::MotionData value) { + OnMotionUpdate(port, type, id, value); + }}; + handle->on_ring_data = {[this](f32 ring_data) { OnRingConUpdate(ring_data); }}; + handle->on_amiibo_data = {[this, port](const std::vector& amiibo_data) { + OnAmiiboUpdate(port, amiibo_data); + }}; + handle->InitializeDevice(); + } +} + +std::shared_ptr Joycons::GetNextFreeHandle( + Joycon::ControllerType type) const { + + if (type == Joycon::ControllerType::Left) { + for (const auto& device : left_joycons) { + if (!device->IsConnected()) { + return device; + } + } + } + if (type == Joycon::ControllerType::Right) { + for (const auto& device : right_joycons) { + if (!device->IsConnected()) { + return device; + } + } + } + if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) { + for (const auto& device : pro_joycons) { + if (!device->IsConnected()) { + return device; + } + } + } + return nullptr; +} + +bool Joycons::IsVibrationEnabled(const PadIdentifier& identifier) { + const auto handle = GetHandle(identifier); + if (handle == nullptr) { + return false; + } + return handle->IsVibrationEnabled(); +} + +Common::Input::VibrationError Joycons::SetVibration( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { + const Joycon::VibrationValue native_vibration{ + .low_amplitude = vibration.low_amplitude, + .low_frequency = vibration.low_frequency, + .high_amplitude = vibration.high_amplitude, + .high_frequency = vibration.high_amplitude, + }; + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return Common::Input::VibrationError::InvalidHandle; + } + + handle->SetVibration(native_vibration); + return Common::Input::VibrationError::None; +} + +void Joycons::SetLeds(const PadIdentifier& identifier, const Common::Input::LedStatus& led_status) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + return; + } + int led_config = led_status.led_1 ? 1 : 0; + led_config += led_status.led_2 ? 2 : 0; + led_config += led_status.led_3 ? 4 : 0; + led_config += led_status.led_4 ? 8 : 0; + + const auto result = handle->SetLedConfig(static_cast(led_config)); + if (result != Joycon::DriverResult::Success) { + LOG_ERROR(Input, "Failed to set led config"); + } +} + +Common::Input::CameraError Joycons::SetCameraFormat(const PadIdentifier& identifier_, + Common::Input::CameraFormat camera_format) { + return Common::Input::CameraError::NotSupported; +}; + +Common::Input::NfcState Joycons::SupportsNfc(const PadIdentifier& identifier_) const { + return Common::Input::NfcState::Success; +}; + +Common::Input::NfcState Joycons::WriteNfcData(const PadIdentifier& identifier_, + const std::vector& data) { + return Common::Input::NfcState::NotSupported; +}; + +Common::Input::PollingError Joycons::SetPollingMode(const PadIdentifier& identifier, + const Common::Input::PollingMode polling_mode) { + auto handle = GetHandle(identifier); + if (handle == nullptr) { + LOG_ERROR(Input, "Invalid handle {}", identifier.port); + return Common::Input::PollingError::InvalidHandle; + } + + switch (polling_mode) { + case Common::Input::PollingMode::NFC: + handle->SetNfcMode(); + break; + case Common::Input::PollingMode::Active: + handle->SetActiveMode(); + break; + case Common::Input::PollingMode::Pasive: + handle->SetPasiveMode(); + break; + case Common::Input::PollingMode::Ring: + handle->SetRingConMode(); + break; + default: + return Common::Input::PollingError::NotSupported; + } + + return Common::Input::PollingError::None; +} + +void Joycons::OnBatteryUpdate(std::size_t port, Joycon::ControllerType type, + Joycon::Battery value) { + const auto identifier = GetIdentifier(port, type); + if (value.charging != 0) { + SetBattery(identifier, Common::Input::BatteryLevel::Charging); + return; + } + + Common::Input::BatteryLevel battery{value.status.Value()}; + switch (value.status) { + case 0: + battery = Common::Input::BatteryLevel::Empty; + break; + case 1: + battery = Common::Input::BatteryLevel::Critical; + break; + case 2: + battery = Common::Input::BatteryLevel::Low; + break; + case 3: + battery = Common::Input::BatteryLevel::Medium; + break; + case 4: + default: + battery = Common::Input::BatteryLevel::Full; + break; + } + SetBattery(identifier, battery); +} + +void Joycons::OnColorUpdate(std::size_t port, Joycon::ControllerType type, + const Joycon::Color& value) {} + +void Joycons::OnButtonUpdate(std::size_t port, Joycon::ControllerType type, int id, bool value) { + const auto identifier = GetIdentifier(port, type); + SetButton(identifier, id, value); +} + +void Joycons::OnStickUpdate(std::size_t port, Joycon::ControllerType type, int id, f32 value) { + const auto identifier = GetIdentifier(port, type); + SetAxis(identifier, id, value); +} + +void Joycons::OnMotionUpdate(std::size_t port, Joycon::ControllerType type, int id, + const Joycon::MotionData& value) { + const auto identifier = GetIdentifier(port, type); + BasicMotion motion_data{ + .gyro_x = value.gyro_x, + .gyro_y = value.gyro_y, + .gyro_z = value.gyro_z, + .accel_x = value.accel_x, + .accel_y = value.accel_y, + .accel_z = value.accel_z, + .delta_timestamp = 15000, + }; + SetMotion(identifier, id, motion_data); +} + +void Joycons::OnRingConUpdate(f32 ring_data) { + // To simplify ring detection it will always be mapped to an empty identifier for all + // controllers + constexpr PadIdentifier identifier = { + .guid = Common::UUID{}, + .port = 0, + .pad = 0, + }; + SetAxis(identifier, 100, ring_data); +} + +void Joycons::OnAmiiboUpdate(std::size_t port, const std::vector& amiibo_data) { + const auto identifier = GetIdentifier(port, Joycon::ControllerType::Right); + SetNfc(identifier, {Common::Input::NfcState::NewAmiibo, amiibo_data}); +} + +std::shared_ptr Joycons::GetHandle(PadIdentifier identifier) const { + auto is_handle_active = [&](std::shared_ptr device) { + if (!device) { + return false; + } + if (!device->IsConnected()) { + return false; + } + if (device->GetDevicePort() == identifier.port) { + return true; + } + return false; + }; + const auto type = static_cast(identifier.pad); + if (type == Joycon::ControllerType::Left) { + for (const auto& device : left_joycons) { + if (is_handle_active(device)) { + return device; + } + } + } + if (type == Joycon::ControllerType::Right) { + for (const auto& device : right_joycons) { + if (is_handle_active(device)) { + return device; + } + } + } + if (type == Joycon::ControllerType::Pro || type == Joycon::ControllerType::Grip) { + for (const auto& device : pro_joycons) { + if (is_handle_active(device)) { + return device; + } + } + } + return nullptr; +} + +PadIdentifier Joycons::GetIdentifier(std::size_t port, Joycon::ControllerType type) const { + return { + .guid = Common::UUID{Common::InvalidUUID}, + .port = port, + .pad = static_cast(type), + }; +} + +std::vector Joycons::GetInputDevices() const { + std::vector devices{}; + + auto add_entry = [&](std::shared_ptr device) { + if (!device) { + return; + } + if (!device->IsConnected()) { + return; + } + std::string name = fmt::format("{} {}", JoyconName(device->GetHandleDeviceType()), + device->GetDevicePort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"port", std::to_string(device->GetDevicePort())}, + {"pad", std::to_string(static_cast(device->GetHandleDeviceType()))}, + }); + }; + + for (const auto& controller : left_joycons) { + add_entry(controller); + } + for (const auto& controller : right_joycons) { + add_entry(controller); + } + for (const auto& controller : pro_joycons) { + add_entry(controller); + } + + return devices; +} + +ButtonMapping Joycons::GetButtonMappingForDevice(const Common::ParamPackage& params) { + static constexpr std::array, 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(Joycon::PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast(Joycon::PadAxes::LeftStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("port", params.Get("port", 0)); + right_analog_params.Set("axis_x", static_cast(Joycon::PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast(Joycon::PadAxes::RightStickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +MotionMapping Joycons::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + MotionMapping mapping = {}; + Common::ParamPackage left_motion_params; + left_motion_params.Set("engine", GetEngineName()); + left_motion_params.Set("port", params.Get("port", 0)); + left_motion_params.Set("motion", 0); + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params)); + Common::ParamPackage right_Motion_params; + right_Motion_params.Set("engine", GetEngineName()); + right_Motion_params.Set("port", params.Get("port", 0)); + right_Motion_params.Set("motion", 1); + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_Motion_params)); + return mapping; +} + +Common::Input::ButtonNames Joycons::GetUIButtonName(const Common::ParamPackage& params) const { + const auto button = static_cast(params.Get("button", 0)); + switch (button) { + case Joycon::PadButton::Left: + return Common::Input::ButtonNames::ButtonLeft; + case Joycon::PadButton::Right: + return Common::Input::ButtonNames::ButtonRight; + case Joycon::PadButton::Down: + return Common::Input::ButtonNames::ButtonDown; + case Joycon::PadButton::Up: + return Common::Input::ButtonNames::ButtonUp; + case Joycon::PadButton::LeftSL: + case Joycon::PadButton::RightSL: + return Common::Input::ButtonNames::TriggerSL; + case Joycon::PadButton::LeftSR: + case Joycon::PadButton::RightSR: + return Common::Input::ButtonNames::TriggerSR; + case Joycon::PadButton::L: + return Common::Input::ButtonNames::TriggerL; + case Joycon::PadButton::R: + return Common::Input::ButtonNames::TriggerR; + case Joycon::PadButton::ZL: + return Common::Input::ButtonNames::TriggerZL; + case Joycon::PadButton::ZR: + return Common::Input::ButtonNames::TriggerZR; + case Joycon::PadButton::A: + return Common::Input::ButtonNames::ButtonA; + case Joycon::PadButton::B: + return Common::Input::ButtonNames::ButtonB; + case Joycon::PadButton::X: + return Common::Input::ButtonNames::ButtonX; + case Joycon::PadButton::Y: + return Common::Input::ButtonNames::ButtonY; + case Joycon::PadButton::Plus: + return Common::Input::ButtonNames::ButtonPlus; + case Joycon::PadButton::Minus: + return Common::Input::ButtonNames::ButtonMinus; + case Joycon::PadButton::Home: + return Common::Input::ButtonNames::ButtonHome; + case Joycon::PadButton::Capture: + return Common::Input::ButtonNames::ButtonCapture; + case Joycon::PadButton::StickL: + return Common::Input::ButtonNames::ButtonStickL; + case Joycon::PadButton::StickR: + return Common::Input::ButtonNames::ButtonStickR; + default: + return Common::Input::ButtonNames::Undefined; + } +} + +Common::Input::ButtonNames Joycons::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +std::string Joycons::JoyconName(Joycon::ControllerType type) const { + switch (type) { + case Joycon::ControllerType::Left: + return "Left Joycon"; + case Joycon::ControllerType::Right: + return "Right Joycon"; + case Joycon::ControllerType::Pro: + return "Pro Controller"; + case Joycon::ControllerType::Grip: + return "Grip Controller"; + default: + return "Unknow Joycon"; + } +} +} // namespace InputCommon -- cgit v1.2.3