// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #include "common/logging/log.h" #include "common/swap.h" #include "common/thread.h" #include "input_common/helpers/joycon_driver.h" namespace InputCommon::Joycon { JoyconDriver::JoyconDriver(std::size_t port_) : port{port_} { hidapi_handle = std::make_shared(); } JoyconDriver::~JoyconDriver() { Stop(); } void JoyconDriver::Stop() { is_connected = false; input_thread = {}; } 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 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 DriverResult::HandleInUse; } SDL_hid_set_nonblocking(hidapi_handle->handle, 1); return DriverResult::Success; } DriverResult JoyconDriver::InitializeDevice() { if (!hidapi_handle->handle) { return DriverResult::InvalidHandle; } std::scoped_lock lock{mutex}; disable_input_thread = true; // Reset Counters error_counter = 0; hidapi_handle->packet_counter = 0; // Set HW default configuration vibration_enabled = true; motion_enabled = true; hidbus_enabled = false; nfc_enabled = false; passive_enabled = false; gyro_sensitivity = Joycon::GyroSensitivity::DPS2000; gyro_performance = Joycon::GyroPerformance::HZ833; accelerometer_sensitivity = Joycon::AccelerometerSensitivity::G8; accelerometer_performance = Joycon::AccelerometerPerformance::HZ100; // Initialize HW Protocols // Get fixed joycon info supported_features = GetSupportedFeatures(); // Get Calibration data // Set led status // Apply HW configuration SetPollingMode(); // Start pooling for data 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 DriverResult::Success; } void JoyconDriver::InputThread(std::stop_token stop_token) { LOG_INFO(Input, "JC Adapter input thread started"); Common::SetCurrentThreadName("JoyconInput"); input_thread_running = true; // Max update rate is 5ms, ensure we are always able to read a bit faster constexpr int ThreadDelay = 2; std::vector buffer(MaxBufferSize); while (!stop_token.stop_requested()) { int status = 0; if (!IsInputThreadValid()) { input_thread.request_stop(); continue; } // By disabling the input thread we can ensure custom commands will succeed as no package is // skipped 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); } std::this_thread::yield(); } is_connected = false; input_thread_running = false; LOG_INFO(Input, "JC Adapter input thread stopped"); } void JoyconDriver::OnNewData(std::span buffer) { const auto report_mode = static_cast(buffer[0]); switch (report_mode) { case InputReport::STANDARD_FULL_60HZ: ReadActiveMode(buffer); break; case InputReport::NFC_IR_MODE_60HZ: ReadNfcIRMode(buffer); break; case InputReport::SIMPLE_HID_MODE: ReadPassiveMode(buffer); break; default: LOG_ERROR(Input, "Report mode not Implemented {}", report_mode); break; } } void JoyconDriver::SetPollingMode() { disable_input_thread = true; disable_input_thread = false; } JoyconDriver::SupportedFeatures JoyconDriver::GetSupportedFeatures() { SupportedFeatures features{ .passive = true, .motion = true, .vibration = true, }; if (device_type == ControllerType::Right) { features.nfc = true; features.irs = true; features.hidbus = true; } if (device_type == ControllerType::Pro) { features.nfc = true; } return features; } void JoyconDriver::ReadActiveMode(std::span buffer) { InputReportActive data{}; memcpy(&data, buffer.data(), sizeof(InputReportActive)); // Packages can be a litte bit inconsistent. Average the delta time to provide a smoother motion // experience const auto now = std::chrono::steady_clock::now(); const auto new_delta_time = std::chrono::duration_cast(now - last_update).count(); delta_time = static_cast((delta_time * 0.8f) + (new_delta_time * 0.2)); last_update = now; switch (device_type) { case Joycon::ControllerType::Left: break; case Joycon::ControllerType::Right: break; case Joycon::ControllerType::Pro: break; case Joycon::ControllerType::Grip: case Joycon::ControllerType::Dual: case Joycon::ControllerType::None: break; } on_battery_data(data.battery_status); on_color_data(color); } void JoyconDriver::ReadPassiveMode(std::span buffer) { InputReportPassive data{}; memcpy(&data, buffer.data(), sizeof(InputReportPassive)); switch (device_type) { case Joycon::ControllerType::Left: break; case Joycon::ControllerType::Right: break; case Joycon::ControllerType::Pro: break; case Joycon::ControllerType::Grip: case Joycon::ControllerType::Dual: case Joycon::ControllerType::None: break; } } void JoyconDriver::ReadNfcIRMode(std::span buffer) { // This mode is compatible with the active mode ReadActiveMode(buffer); if (!nfc_enabled) { return; } } bool JoyconDriver::IsInputThreadValid() const { if (!is_connected) { return false; } if (hidapi_handle->handle == nullptr) { return false; } // Controller is not responding. Terminate connection if (error_counter > MaxErrorCount) { return false; } return true; } bool JoyconDriver::IsPayloadCorrect(int status, std::span buffer) { if (status <= -1) { error_counter++; return false; } // There's no new data if (status == 0) { return false; } // No reply ever starts with zero if (buffer[0] == 0x00) { error_counter++; return false; } error_counter = 0; return true; } DriverResult JoyconDriver::SetVibration(const VibrationValue& vibration) { std::scoped_lock lock{mutex}; return DriverResult::NotSupported; } DriverResult JoyconDriver::SetLedConfig(u8 led_pattern) { std::scoped_lock lock{mutex}; return DriverResult::NotSupported; } DriverResult JoyconDriver::SetPasiveMode() { motion_enabled = false; hidbus_enabled = false; nfc_enabled = false; passive_enabled = true; SetPollingMode(); return DriverResult::Success; } DriverResult JoyconDriver::SetActiveMode() { motion_enabled = false; hidbus_enabled = false; nfc_enabled = false; passive_enabled = false; SetPollingMode(); return DriverResult::Success; } DriverResult JoyconDriver::SetNfcMode() { motion_enabled = false; hidbus_enabled = false; nfc_enabled = true; passive_enabled = false; SetPollingMode(); return DriverResult::Success; } DriverResult JoyconDriver::SetRingConMode() { motion_enabled = true; hidbus_enabled = true; nfc_enabled = false; passive_enabled = false; SetPollingMode(); return DriverResult::Success; } bool JoyconDriver::IsConnected() const { std::scoped_lock lock{mutex}; return is_connected; } 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 handle_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; } Joycon::DriverResult JoyconDriver::GetDeviceType(SDL_hid_device_info* device_info, ControllerType& controller_type) { std::array, 4> supported_devices{ std::pair{0x2006, Joycon::ControllerType::Left}, {0x2007, Joycon::ControllerType::Right}, {0x2009, Joycon::ControllerType::Pro}, {0x200E, Joycon::ControllerType::Grip}, }; constexpr u16 nintendo_vendor_id = 0x057e; controller_type = Joycon::ControllerType::None; if (device_info->vendor_id != nintendo_vendor_id) { return Joycon::DriverResult::UnsupportedControllerType; } for (const auto& [product_id, type] : supported_devices) { if (device_info->product_id == static_cast(product_id)) { controller_type = type; return Joycon::DriverResult::Success; } } return Joycon::DriverResult::UnsupportedControllerType; } Joycon::DriverResult JoyconDriver::GetSerialNumber(SDL_hid_device_info* device_info, Joycon::SerialNumber& serial_number) { if (device_info->serial_number == nullptr) { return Joycon::DriverResult::Unknown; } std::memcpy(&serial_number, device_info->serial_number, 15); return Joycon::DriverResult::Success; } } // namespace InputCommon::Joycon