From d80e6c399bf8196646cca5ac1265d122638bb96b Mon Sep 17 00:00:00 2001
From: Narr the Reg <juangerman-13@hotmail.com>
Date: Tue, 20 Dec 2022 11:34:33 -0600
Subject: input_common: Initial skeleton for custom joycon driver

---
 src/input_common/helpers/joycon_driver.cpp | 382 +++++++++++++++++++++++++++++
 1 file changed, 382 insertions(+)
 create mode 100644 src/input_common/helpers/joycon_driver.cpp

(limited to 'src/input_common/helpers/joycon_driver.cpp')

diff --git a/src/input_common/helpers/joycon_driver.cpp b/src/input_common/helpers/joycon_driver.cpp
new file mode 100644
index 0000000000..a0a2a180b5
--- /dev/null
+++ b/src/input_common/helpers/joycon_driver.cpp
@@ -0,0 +1,382 @@
+// 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<JoyconHandle>();
+}
+
+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<u8> 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<u8> buffer) {
+    const auto report_mode = static_cast<InputReport>(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<u8> 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<std::chrono::microseconds>(now - last_update).count();
+    delta_time = static_cast<u64>((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<u8> 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<u8> 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<const u8> 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<std::pair<u32, Joycon::ControllerType>, 4> supported_devices{
+        std::pair<u32, Joycon::ControllerType>{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<u16>(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
-- 
cgit v1.2.3-70-g09d2