| |
| |
|
|
| #include "common/input.h" |
| #include "common/logging/log.h" |
| #include "common/scope_exit.h" |
| #include "common/swap.h" |
| #include "common/thread.h" |
| #include "input_common/helpers/joycon_driver.h" |
| #include "input_common/helpers/joycon_protocol/calibration.h" |
| #include "input_common/helpers/joycon_protocol/generic_functions.h" |
| #include "input_common/helpers/joycon_protocol/irs.h" |
| #include "input_common/helpers/joycon_protocol/nfc.h" |
| #include "input_common/helpers/joycon_protocol/poller.h" |
| #include "input_common/helpers/joycon_protocol/ringcon.h" |
| #include "input_common/helpers/joycon_protocol/rumble.h" |
|
|
| namespace InputCommon::Joycon { |
| JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { |
| hidapi_handle = std::make_shared<JoyconHandle>(); |
| } |
|
|
| JoyconDriver::~JoyconDriver() { |
| Stop(); |
| } |
|
|
| void JoyconDriver::Stop() { |
| is_connected = false; |
| input_thread = {}; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::RequestDeviceAccess(SDL_hid_device_info* device_info) { |
| std::scoped_lock lock{mutex}; |
|
|
| handle_device_type = ControllerType::None; |
| GetDeviceType(device_info, handle_device_type); |
| if (handle_device_type == ControllerType::None) { |
| return Common::Input::DriverResult::UnsupportedControllerType; |
| } |
|
|
| hidapi_handle->handle = |
| SDL_hid_open(device_info->vendor_id, device_info->product_id, device_info->serial_number); |
| std::memcpy(&handle_serial_number, device_info->serial_number, 15); |
| if (!hidapi_handle->handle) { |
| LOG_ERROR(Input, "Yuzu can't gain access to this device: ID {:04X}:{:04X}.", |
| device_info->vendor_id, device_info->product_id); |
| return Common::Input::DriverResult::HandleInUse; |
| } |
| SDL_hid_set_nonblocking(hidapi_handle->handle, 1); |
| return Common::Input::DriverResult::Success; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::InitializeDevice() { |
| if (!hidapi_handle->handle) { |
| return Common::Input::DriverResult::InvalidHandle; |
| } |
| std::scoped_lock lock{mutex}; |
| disable_input_thread = true; |
|
|
| |
| error_counter = 0; |
| hidapi_handle->packet_counter = 0; |
|
|
| |
| starlink_connected = false; |
| ring_connected = false; |
| amiibo_detected = false; |
|
|
| |
| vibration_enabled = true; |
| motion_enabled = true; |
| hidbus_enabled = false; |
| nfc_enabled = false; |
| passive_enabled = false; |
| irs_enabled = false; |
| input_only_device = false; |
| gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; |
| gyro_performance = Joycon::GyroPerformance::HZ833; |
| accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; |
| accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; |
|
|
| |
| calibration_protocol = std::make_unique<CalibrationProtocol>(hidapi_handle); |
| generic_protocol = std::make_unique<GenericProtocol>(hidapi_handle); |
| irs_protocol = std::make_unique<IrsProtocol>(hidapi_handle); |
| nfc_protocol = std::make_unique<NfcProtocol>(hidapi_handle); |
| ring_protocol = std::make_unique<RingConProtocol>(hidapi_handle); |
| rumble_protocol = std::make_unique<RumbleProtocol>(hidapi_handle); |
|
|
| |
| if (generic_protocol->GetVersionNumber(version) != Common::Input::DriverResult::Success) { |
| |
| input_only_device = true; |
| } |
|
|
| if (!input_only_device) { |
| generic_protocol->SetLowPowerMode(false); |
| generic_protocol->GetColor(color); |
| if (handle_device_type == ControllerType::Pro) { |
| |
| generic_protocol->GetControllerType(device_type); |
| } else { |
| device_type = handle_device_type; |
| } |
| generic_protocol->GetSerialNumber(serial_number); |
| } |
|
|
| supported_features = GetSupportedFeatures(); |
|
|
| |
| calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration); |
| calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration); |
| calibration_protocol->GetImuCalibration(motion_calibration); |
|
|
| |
| generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port)); |
|
|
| |
| SetPollingMode(); |
|
|
| |
| joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration, |
| right_stick_calibration, motion_calibration); |
|
|
| |
| is_connected = true; |
| if (!input_thread_running) { |
| input_thread = |
| std::jthread([this](std::stop_token stop_token) { InputThread(stop_token); }); |
| } |
|
|
| disable_input_thread = false; |
| return Common::Input::DriverResult::Success; |
| } |
|
|
| void JoyconDriver::InputThread(std::stop_token stop_token) { |
| LOG_INFO(Input, "Joycon Adapter input thread started"); |
| Common::SetCurrentThreadName("JoyconInput"); |
| input_thread_running = true; |
|
|
| |
| constexpr int ThreadDelay = 3; |
| std::vector<u8> buffer(MaxBufferSize); |
|
|
| while (!stop_token.stop_requested()) { |
| int status = 0; |
|
|
| if (!IsInputThreadValid()) { |
| input_thread.request_stop(); |
| continue; |
| } |
|
|
| |
| |
| if (!disable_input_thread) { |
| status = SDL_hid_read_timeout(hidapi_handle->handle, buffer.data(), buffer.size(), |
| ThreadDelay); |
| } else { |
| std::this_thread::sleep_for(std::chrono::milliseconds(ThreadDelay)); |
| } |
|
|
| if (IsPayloadCorrect(status, buffer)) { |
| OnNewData(buffer); |
| } |
|
|
| if (!vibration_queue.Empty()) { |
| VibrationValue vibration_value; |
| vibration_queue.Pop(vibration_value); |
| last_vibration_result = rumble_protocol->SendVibration(vibration_value); |
| } |
|
|
| |
| while (vibration_queue.Size() > 6) { |
| vibration_queue.Pop(); |
| } |
|
|
| std::this_thread::yield(); |
| } |
|
|
| is_connected = false; |
| input_thread_running = false; |
| LOG_INFO(Input, "Joycon Adapter input thread stopped"); |
| } |
|
|
| void JoyconDriver::OnNewData(std::span<u8> buffer) { |
| const auto report_mode = static_cast<ReportMode>(buffer[0]); |
|
|
| |
| |
| switch (report_mode) { |
| case ReportMode::STANDARD_FULL_60HZ: |
| case ReportMode::NFC_IR_MODE_60HZ: |
| case ReportMode::SIMPLE_HID_MODE: { |
| const auto now = std::chrono::steady_clock::now(); |
| const auto new_delta_time = static_cast<u64>( |
| std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); |
| delta_time = ((delta_time * 8) + (new_delta_time * 2)) / 10; |
| last_update = now; |
| joycon_poller->UpdateColor(color); |
| break; |
| } |
| default: |
| break; |
| } |
|
|
| const MotionStatus motion_status{ |
| .is_enabled = motion_enabled, |
| .delta_time = delta_time, |
| .gyro_sensitivity = gyro_sensitivity, |
| .accelerometer_sensitivity = accelerometer_sensitivity, |
| }; |
|
|
| |
| if (ring_connected && report_mode == ReportMode::STANDARD_FULL_60HZ) { |
| InputReportActive data{}; |
| memcpy(&data, buffer.data(), sizeof(InputReportActive)); |
| calibration_protocol->GetRingCalibration(ring_calibration, data.ring_input); |
| } |
|
|
| const RingStatus ring_status{ |
| .is_enabled = ring_connected, |
| .default_value = ring_calibration.default_value, |
| .max_value = ring_calibration.max_value, |
| .min_value = ring_calibration.min_value, |
| }; |
|
|
| if (irs_protocol->IsEnabled()) { |
| irs_protocol->RequestImage(buffer); |
| joycon_poller->UpdateCamera(irs_protocol->GetImage(), irs_protocol->GetIrsFormat()); |
| } |
|
|
| if (nfc_protocol->IsPolling()) { |
| if (amiibo_detected) { |
| if (!nfc_protocol->HasAmiibo()) { |
| joycon_poller->UpdateAmiibo({}); |
| amiibo_detected = false; |
| return; |
| } |
| } |
|
|
| if (!amiibo_detected) { |
| Joycon::TagInfo tag_info; |
| const auto result = nfc_protocol->GetTagInfo(tag_info); |
| if (result == Common::Input::DriverResult::Success) { |
| joycon_poller->UpdateAmiibo(tag_info); |
| amiibo_detected = true; |
| } |
| } |
| } |
|
|
| switch (report_mode) { |
| case ReportMode::STANDARD_FULL_60HZ: |
| joycon_poller->ReadActiveMode(buffer, motion_status, ring_status); |
| break; |
| case ReportMode::NFC_IR_MODE_60HZ: |
| joycon_poller->ReadNfcIRMode(buffer, motion_status); |
| break; |
| case ReportMode::SIMPLE_HID_MODE: |
| joycon_poller->ReadPassiveMode(buffer); |
| break; |
| case ReportMode::SUBCMD_REPLY: |
| LOG_DEBUG(Input, "Unhandled command reply"); |
| break; |
| default: |
| LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); |
| break; |
| } |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetPollingMode() { |
| SCOPE_EXIT { |
| disable_input_thread = false; |
| }; |
| disable_input_thread = true; |
|
|
| rumble_protocol->EnableRumble(vibration_enabled && supported_features.vibration); |
|
|
| if (motion_enabled && supported_features.motion) { |
| generic_protocol->EnableImu(true); |
| generic_protocol->SetImuConfig(gyro_sensitivity, gyro_performance, |
| accelerometer_sensitivity, accelerometer_performance); |
| } else { |
| generic_protocol->EnableImu(false); |
| } |
|
|
| if (input_only_device) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
|
|
| if (irs_protocol->IsEnabled()) { |
| irs_protocol->DisableIrs(); |
| } |
|
|
| if (nfc_protocol->IsEnabled()) { |
| amiibo_detected = false; |
| nfc_protocol->DisableNfc(); |
| } |
|
|
| if (ring_protocol->IsEnabled()) { |
| ring_connected = false; |
| ring_protocol->DisableRingCon(); |
| } |
|
|
| if (irs_enabled && supported_features.irs) { |
| auto result = irs_protocol->EnableIrs(); |
| if (result == Common::Input::DriverResult::Success) { |
| return result; |
| } |
| irs_protocol->DisableIrs(); |
| LOG_ERROR(Input, "Error enabling IRS"); |
| return result; |
| } |
|
|
| if (nfc_enabled && supported_features.nfc) { |
| auto result = nfc_protocol->EnableNfc(); |
| if (result == Common::Input::DriverResult::Success) { |
| return result; |
| } |
| nfc_protocol->DisableNfc(); |
| LOG_ERROR(Input, "Error enabling NFC"); |
| return result; |
| } |
|
|
| if (hidbus_enabled && supported_features.hidbus) { |
| auto result = ring_protocol->EnableRingCon(); |
| if (result == Common::Input::DriverResult::Success) { |
| result = ring_protocol->StartRingconPolling(); |
| } |
| if (result == Common::Input::DriverResult::Success) { |
| ring_connected = true; |
| return result; |
| } |
| ring_connected = false; |
| ring_protocol->DisableRingCon(); |
| LOG_ERROR(Input, "Error enabling Ringcon"); |
| return result; |
| } |
|
|
| if (passive_enabled && supported_features.passive) { |
| const auto result = generic_protocol->EnablePassiveMode(); |
| if (result == Common::Input::DriverResult::Success) { |
| return result; |
| } |
| LOG_ERROR(Input, "Error enabling passive mode"); |
| } |
|
|
| |
| const auto result = generic_protocol->EnableActiveMode(); |
| if (result != Common::Input::DriverResult::Success) { |
| LOG_ERROR(Input, "Error enabling active mode"); |
| } |
| |
| generic_protocol->TriggersElapsed(); |
|
|
| return result; |
| } |
|
|
| JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { |
| SupportedFeatures features{ |
| .passive = true, |
| .motion = true, |
| .vibration = true, |
| }; |
|
|
| if (input_only_device) { |
| return features; |
| } |
|
|
| if (device_type == ControllerType::Right) { |
| features.nfc = true; |
| features.irs = true; |
| features.hidbus = true; |
| } |
|
|
| if (device_type == ControllerType::Pro) { |
| features.nfc = true; |
| } |
| return features; |
| } |
|
|
| bool JoyconDriver::IsInputThreadValid() const { |
| if (!is_connected.load()) { |
| return false; |
| } |
| if (hidapi_handle->handle == nullptr) { |
| return false; |
| } |
| |
| if (error_counter > MaxErrorCount) { |
| return false; |
| } |
| return true; |
| } |
|
|
| bool JoyconDriver::IsPayloadCorrect(int status, std::span<const u8> buffer) { |
| if (status <= -1) { |
| error_counter++; |
| return false; |
| } |
| |
| if (status == 0) { |
| return false; |
| } |
| |
| if (buffer[0] == 0x00) { |
| error_counter++; |
| return false; |
| } |
| error_counter = 0; |
| return true; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { |
| std::scoped_lock lock{mutex}; |
| if (disable_input_thread) { |
| return Common::Input::DriverResult::HandleInUse; |
| } |
| vibration_queue.Push(vibration); |
| return last_vibration_result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { |
| std::scoped_lock lock{mutex}; |
| if (disable_input_thread) { |
| return Common::Input::DriverResult::HandleInUse; |
| } |
| return generic_protocol->SetLedPattern(led_pattern); |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetIrsConfig(IrsMode mode_, IrsResolution format_) { |
| std::scoped_lock lock{mutex}; |
| if (disable_input_thread) { |
| return Common::Input::DriverResult::HandleInUse; |
| } |
| disable_input_thread = true; |
| const auto result = irs_protocol->SetIrsConfig(mode_, format_); |
| disable_input_thread = false; |
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetPassiveMode() { |
| std::scoped_lock lock{mutex}; |
| motion_enabled = false; |
| hidbus_enabled = false; |
| nfc_enabled = false; |
| passive_enabled = true; |
| irs_enabled = false; |
| return SetPollingMode(); |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetActiveMode() { |
| if (is_ring_disabled_by_irs) { |
| is_ring_disabled_by_irs = false; |
| SetActiveMode(); |
| return SetRingConMode(); |
| } |
|
|
| std::scoped_lock lock{mutex}; |
| motion_enabled = true; |
| hidbus_enabled = false; |
| nfc_enabled = false; |
| passive_enabled = false; |
| irs_enabled = false; |
| return SetPollingMode(); |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetIrMode() { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.irs) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
|
|
| if (ring_connected) { |
| is_ring_disabled_by_irs = true; |
| } |
|
|
| motion_enabled = false; |
| hidbus_enabled = false; |
| nfc_enabled = false; |
| passive_enabled = false; |
| irs_enabled = true; |
| return SetPollingMode(); |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetNfcMode() { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
|
|
| motion_enabled = true; |
| hidbus_enabled = false; |
| nfc_enabled = true; |
| passive_enabled = false; |
| irs_enabled = false; |
| return SetPollingMode(); |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::SetRingConMode() { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.hidbus) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
|
|
| motion_enabled = true; |
| hidbus_enabled = true; |
| nfc_enabled = false; |
| passive_enabled = false; |
| irs_enabled = false; |
|
|
| const auto result = SetPollingMode(); |
|
|
| if (!ring_connected) { |
| return Common::Input::DriverResult::NoDeviceDetected; |
| } |
|
|
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::StartNfcPolling() { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
| if (!nfc_protocol->IsEnabled()) { |
| return Common::Input::DriverResult::Disabled; |
| } |
|
|
| disable_input_thread = true; |
| const auto result = nfc_protocol->StartNFCPollingMode(); |
| disable_input_thread = false; |
|
|
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::StopNfcPolling() { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
| if (!nfc_protocol->IsEnabled()) { |
| return Common::Input::DriverResult::Disabled; |
| } |
|
|
| disable_input_thread = true; |
| const auto result = nfc_protocol->StopNFCPollingMode(); |
| disable_input_thread = false; |
|
|
| if (amiibo_detected) { |
| amiibo_detected = false; |
| joycon_poller->UpdateAmiibo({}); |
| } |
|
|
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::ReadAmiiboData(std::vector<u8>& out_data) { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
| if (!nfc_protocol->IsEnabled()) { |
| return Common::Input::DriverResult::Disabled; |
| } |
| if (!amiibo_detected) { |
| return Common::Input::DriverResult::ErrorWritingData; |
| } |
|
|
| out_data.resize(0x21C); |
| disable_input_thread = true; |
| const auto result = nfc_protocol->ReadAmiibo(out_data); |
| disable_input_thread = false; |
|
|
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::WriteNfcData(std::span<const u8> data) { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
| if (!nfc_protocol->IsEnabled()) { |
| return Common::Input::DriverResult::Disabled; |
| } |
| if (!amiibo_detected) { |
| return Common::Input::DriverResult::ErrorWritingData; |
| } |
|
|
| disable_input_thread = true; |
| const auto result = nfc_protocol->WriteAmiibo(data); |
| disable_input_thread = false; |
|
|
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::ReadMifareData(std::span<const MifareReadChunk> data, |
| std::span<MifareReadData> out_data) { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
| if (!nfc_protocol->IsEnabled()) { |
| return Common::Input::DriverResult::Disabled; |
| } |
| if (!amiibo_detected) { |
| return Common::Input::DriverResult::ErrorWritingData; |
| } |
|
|
| disable_input_thread = true; |
| const auto result = nfc_protocol->ReadMifare(data, out_data); |
| disable_input_thread = false; |
|
|
| return result; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::WriteMifareData(std::span<const MifareWriteChunk> data) { |
| std::scoped_lock lock{mutex}; |
|
|
| if (!supported_features.nfc) { |
| return Common::Input::DriverResult::NotSupported; |
| } |
| if (!nfc_protocol->IsEnabled()) { |
| return Common::Input::DriverResult::Disabled; |
| } |
| if (!amiibo_detected) { |
| return Common::Input::DriverResult::ErrorWritingData; |
| } |
|
|
| disable_input_thread = true; |
| const auto result = nfc_protocol->WriteMifare(data); |
| disable_input_thread = false; |
|
|
| return result; |
| } |
|
|
| bool JoyconDriver::IsConnected() const { |
| std::scoped_lock lock{mutex}; |
| return is_connected.load(); |
| } |
|
|
| bool JoyconDriver::IsVibrationEnabled() const { |
| std::scoped_lock lock{mutex}; |
| return vibration_enabled; |
| } |
|
|
| FirmwareVersion JoyconDriver::GetDeviceVersion() const { |
| std::scoped_lock lock{mutex}; |
| return version; |
| } |
|
|
| Color JoyconDriver::GetDeviceColor() const { |
| std::scoped_lock lock{mutex}; |
| return color; |
| } |
|
|
| std::size_t JoyconDriver::GetDevicePort() const { |
| std::scoped_lock lock{mutex}; |
| return port; |
| } |
|
|
| ControllerType JoyconDriver::GetDeviceType() const { |
| std::scoped_lock lock{mutex}; |
| return device_type; |
| } |
|
|
| ControllerType JoyconDriver::GetHandleDeviceType() const { |
| std::scoped_lock lock{mutex}; |
| return handle_device_type; |
| } |
|
|
| SerialNumber JoyconDriver::GetSerialNumber() const { |
| std::scoped_lock lock{mutex}; |
| return serial_number; |
| } |
|
|
| SerialNumber JoyconDriver::GetHandleSerialNumber() const { |
| std::scoped_lock lock{mutex}; |
| return handle_serial_number; |
| } |
|
|
| void JoyconDriver::SetCallbacks(const JoyconCallbacks& callbacks) { |
| joycon_poller->SetCallbacks(callbacks); |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, |
| ControllerType& controller_type) { |
| static constexpr std::array<std::pair<u32, ControllerType>, 6> supported_devices{ |
| std::pair<u32, ControllerType>{0x2006, ControllerType::Left}, |
| {0x2007, ControllerType::Right}, |
| {0x2009, ControllerType::Pro}, |
| }; |
| constexpr u16 nintendo_vendor_id = 0x057e; |
|
|
| controller_type = ControllerType::None; |
| if (device_info->vendor_id != nintendo_vendor_id) { |
| return Common::Input::DriverResult::UnsupportedControllerType; |
| } |
|
|
| for (const auto& [product_id, type] : supported_devices) { |
| if (device_info->product_id == static_cast<u16>(product_id)) { |
| controller_type = type; |
| return Common::Input::DriverResult::Success; |
| } |
| } |
| return Common::Input::DriverResult::UnsupportedControllerType; |
| } |
|
|
| Common::Input::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, |
| SerialNumber& serial_number) { |
| if (device_info->serial_number == nullptr) { |
| return Common::Input::DriverResult::Unknown; |
| } |
| std::memcpy(&serial_number, device_info->serial_number, 15); |
| return Common::Input::DriverResult::Success; |
| } |
|
|
| } |
|
|