code:
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <random>
#include <boost/asio.hpp>
#include <fmt/format.h>

#include "common/logging/log.h"
#include "common/param_package.h"
#include "common/settings.h"
#include "input_common/drivers/udp_client.h"
#include "input_common/helpers/udp_protocol.h"

using boost::asio::ip::udp;

namespace InputCommon::CemuhookUDP {

struct SocketCallback {
    std::function<void(Response::Version)> version;
    std::function<void(Response::PortInfo)> port_info;
    std::function<void(Response::PadData)> pad_data;
};

class Socket {
public:
    using clock = std::chrono::system_clock;

    explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
        : callback(std::move(callback_)), timer(io_service),
          socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
        boost::system::error_code ec{};
        auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
        if (ec.value() != boost::system::errc::success) {
            LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
            ipv4 = boost::asio::ip::address_v4{};
        }

        send_endpoint = {udp::endpoint(ipv4, port)};
    }

    void Stop() {
        io_service.stop();
    }

    void Loop() {
        io_service.run();
    }

    void StartSend(const clock::time_point& from) {
        timer.expires_at(from + std::chrono::seconds(3));
        timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
    }

    void StartReceive() {
        socket.async_receive_from(
            boost::asio::buffer(receive_buffer), receive_endpoint,
            [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
                HandleReceive(error, bytes_transferred);
            });
    }

private:
    u32 GenerateRandomClientId() const {
        std::random_device device;
        return device();
    }

    void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
        if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
            switch (*type) {
            case Type::Version: {
                Response::Version version;
                std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
                callback.version(std::move(version));
                break;
            }
            case Type::PortInfo: {
                Response::PortInfo port_info;
                std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
                            sizeof(Response::PortInfo));
                callback.port_info(std::move(port_info));
                break;
            }
            case Type::PadData: {
                Response::PadData pad_data;
                std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
                callback.pad_data(std::move(pad_data));
                break;
            }
            }
        }
        StartReceive();
    }

    void HandleSend(const boost::system::error_code&) {
        boost::system::error_code _ignored{};
        // Send a request for getting port info for the pad
        const Request::PortInfo port_info{4, {0, 1, 2, 3}};
        const auto port_message = Request::Create(port_info, client_id);
        std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
        socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);

        // Send a request for getting pad data for the pad
        const Request::PadData pad_data{
            Request::RegisterFlags::AllPads,
            0,
            EMPTY_MAC_ADDRESS,
        };
        const auto pad_message = Request::Create(pad_data, client_id);
        std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
        socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
        StartSend(timer.expiry());
    }

    SocketCallback callback;
    boost::asio::io_service io_service;
    boost::asio::basic_waitable_timer<clock> timer;
    udp::socket socket;

    const u32 client_id;

    static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
    static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
    std::array<u8, PORT_INFO_SIZE> send_buffer1;
    std::array<u8, PAD_DATA_SIZE> send_buffer2;
    udp::endpoint send_endpoint;

    std::array<u8, MAX_PACKET_SIZE> receive_buffer;
    udp::endpoint receive_endpoint;
};

static void SocketLoop(Socket* socket) {
    socket->StartReceive();
    socket->StartSend(Socket::clock::now());
    socket->Loop();
}

UDPClient::UDPClient(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
    LOG_INFO(Input, "Udp Initialization started");
    ReloadSockets();
}

UDPClient::~UDPClient() {
    Reset();
}

UDPClient::ClientConnection::ClientConnection() = default;

UDPClient::ClientConnection::~ClientConnection() = default;

void UDPClient::ReloadSockets() {
    Reset();

    std::stringstream servers_ss(Settings::values.udp_input_servers.GetValue());
    std::string server_token;
    std::size_t client = 0;
    while (std::getline(servers_ss, server_token, ',')) {
        if (client == MAX_UDP_CLIENTS) {
            break;
        }
        std::stringstream server_ss(server_token);
        std::string token;
        std::getline(server_ss, token, ':');
        std::string udp_input_address = token;
        std::getline(server_ss, token, ':');
        char* temp;
        const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
        if (*temp != '\0') {
            LOG_ERROR(Input, "Port number is not valid {}", token);
            continue;
        }

        const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
        if (client_number != MAX_UDP_CLIENTS) {
            LOG_ERROR(Input, "Duplicated UDP servers found");
            continue;
        }
        StartCommunication(client++, udp_input_address, udp_input_port);
    }
}

std::size_t UDPClient::GetClientNumber(std::string_view host, u16 port) const {
    for (std::size_t client = 0; client < clients.size(); client++) {
        if (clients[client].active == -1) {
            continue;
        }
        if (clients[client].host == host && clients[client].port == port) {
            return client;
        }
    }
    return MAX_UDP_CLIENTS;
}

Common::Input::BatteryLevel UDPClient::GetBatteryLevel(Response::Battery battery) const {
    switch (battery) {
    case Response::Battery::Dying:
        return Common::Input::BatteryLevel::Empty;
    case Response::Battery::Low:
        return Common::Input::BatteryLevel::Critical;
    case Response::Battery::Medium:
        return Common::Input::BatteryLevel::Low;
    case Response::Battery::High:
        return Common::Input::BatteryLevel::Medium;
    case Response::Battery::Full:
    case Response::Battery::Charged:
        return Common::Input::BatteryLevel::Full;
    case Response::Battery::Charging:
    default:
        return Common::Input::BatteryLevel::Charging;
    }
}

void UDPClient::OnVersion([[maybe_unused]] Response::Version data) {
    LOG_TRACE(Input, "Version packet received: {}", data.version);
}

void UDPClient::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
    LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
}

void UDPClient::OnPadData(Response::PadData data, std::size_t client) {
    const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;

    if (pad_index >= pads.size()) {
        LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
        return;
    }

    LOG_TRACE(Input, "PadData packet received");
    if (data.packet_counter == pads[pad_index].packet_sequence) {
        LOG_WARNING(
            Input,
            "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
            pads[pad_index].packet_sequence, data.packet_counter);
        pads[pad_index].connected = false;
        return;
    }

    clients[client].active = 1;
    pads[pad_index].connected = true;
    pads[pad_index].packet_sequence = data.packet_counter;

    const auto now = std::chrono::steady_clock::now();
    const auto time_difference = static_cast<u64>(
        std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
            .count());
    pads[pad_index].last_update = now;

    // Gyroscope values are not it the correct scale from better joy.
    // Dividing by 312 allows us to make one full turn = 1 turn
    // This must be a configurable valued called sensitivity
    const float gyro_scale = 1.0f / 312.0f;

    const BasicMotion motion{
        .gyro_x = data.gyro.pitch * gyro_scale,
        .gyro_y = data.gyro.roll * gyro_scale,
        .gyro_z = -data.gyro.yaw * gyro_scale,
        .accel_x = data.accel.x,
        .accel_y = -data.accel.z,
        .accel_z = data.accel.y,
        .delta_timestamp = time_difference,
    };
    const PadIdentifier identifier = GetPadIdentifier(pad_index);
    SetMotion(identifier, 0, motion);

    for (std::size_t id = 0; id < data.touch.size(); ++id) {
        const auto touch_pad = data.touch[id];
        const auto touch_axis_x_id =
            static_cast<int>(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X);
        const auto touch_axis_y_id =
            static_cast<int>(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y);
        const auto touch_button_id =
            static_cast<int>(id == 0 ? PadButton::Touch1 : PadButton::Touch2);

        // TODO: Use custom calibration per device
        const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
        const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
        const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
        const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
        const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));

        const f32 x =
            static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
            static_cast<f32>(max_x - min_x);
        const f32 y =
            static_cast<f32>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
            static_cast<f32>(max_y - min_y);

        if (touch_pad.is_active) {
            SetAxis(identifier, touch_axis_x_id, x);
            SetAxis(identifier, touch_axis_y_id, y);
            SetButton(identifier, touch_button_id, true);
            continue;
        }
        SetAxis(identifier, touch_axis_x_id, 0);
        SetAxis(identifier, touch_axis_y_id, 0);
        SetButton(identifier, touch_button_id, false);
    }

    SetAxis(identifier, static_cast<int>(PadAxes::LeftStickX),
            (data.left_stick_x - 127.0f) / 127.0f);
    SetAxis(identifier, static_cast<int>(PadAxes::LeftStickY),
            (data.left_stick_y - 127.0f) / 127.0f);
    SetAxis(identifier, static_cast<int>(PadAxes::RightStickX),
            (data.right_stick_x - 127.0f) / 127.0f);
    SetAxis(identifier, static_cast<int>(PadAxes::RightStickY),
            (data.right_stick_y - 127.0f) / 127.0f);

    static constexpr std::array<PadButton, 16> buttons{
        PadButton::Share,    PadButton::L3,     PadButton::R3,    PadButton::Options,
        PadButton::Up,       PadButton::Right,  PadButton::Down,  PadButton::Left,
        PadButton::L2,       PadButton::R2,     PadButton::L1,    PadButton::R1,
        PadButton::Triangle, PadButton::Circle, PadButton::Cross, PadButton::Square};

    for (std::size_t i = 0; i < buttons.size(); ++i) {
        const bool button_status = (data.digital_button & (1U << i)) != 0;
        const int button = static_cast<int>(buttons[i]);
        SetButton(identifier, button, button_status);
    }

    SetButton(identifier, static_cast<int>(PadButton::Home), data.home != 0);
    SetButton(identifier, static_cast<int>(PadButton::TouchHardPress), data.touch_hard_press != 0);

    SetBattery(identifier, GetBatteryLevel(data.info.battery));
}

void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) {
    SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
                            [this](Response::PortInfo info) { OnPortInfo(info); },
                            [this, client](Response::PadData data) { OnPadData(data, client); }};
    LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
    clients[client].uuid = GetHostUUID(host);
    clients[client].host = host;
    clients[client].port = port;
    clients[client].active = 0;
    clients[client].socket = std::make_unique<Socket>(host, port, callback);
    clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
    for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
        const PadIdentifier identifier = GetPadIdentifier(client * PADS_PER_CLIENT + index);
        PreSetController(identifier);
        PreSetMotion(identifier, 0);
    }
}

PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const {
    const std::size_t client = pad_index / PADS_PER_CLIENT;
    return {
        .guid = clients[client].uuid,
        .port = static_cast<std::size_t>(clients[client].port),
        .pad = pad_index,
    };
}

Common::UUID UDPClient::GetHostUUID(const std::string& host) const {
    const auto ip = boost::asio::ip::make_address_v4(host);
    const auto hex_host = fmt::format("00000000-0000-0000-0000-0000{:06x}", ip.to_uint());
    return Common::UUID{hex_host};
}

void UDPClient::Reset() {
    for (auto& client : clients) {
        if (client.thread.joinable()) {
            client.active = -1;
            client.socket->Stop();
            client.thread.join();
        }
    }
}

std::vector<Common::ParamPackage> UDPClient::GetInputDevices() const {
    std::vector<Common::ParamPackage> devices;
    if (!Settings::values.enable_udp_controller) {
        return devices;
    }
    for (std::size_t client = 0; client < clients.size(); client++) {
        if (clients[client].active != 1) {
            continue;
        }
        for (std::size_t index = 0; index < PADS_PER_CLIENT; ++index) {
            const std::size_t pad_index = client * PADS_PER_CLIENT + index;
            if (!pads[pad_index].connected) {
                continue;
            }
            const auto pad_identifier = GetPadIdentifier(pad_index);
            Common::ParamPackage identifier{};
            identifier.Set("engine", GetEngineName());
            identifier.Set("display", fmt::format("UDP Controller {}", pad_identifier.pad));
            identifier.Set("guid", pad_identifier.guid.RawString());
            identifier.Set("port", static_cast<int>(pad_identifier.port));
            identifier.Set("pad", static_cast<int>(pad_identifier.pad));
            devices.emplace_back(identifier);
        }
    }
    return devices;
}

ButtonMapping UDPClient::GetButtonMappingForDevice(const Common::ParamPackage& params) {
    // This list excludes any button that can't be really mapped
    static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 22>
        switch_to_dsu_button = {
            std::pair{Settings::NativeButton::A, PadButton::Circle},
            {Settings::NativeButton::B, PadButton::Cross},
            {Settings::NativeButton::X, PadButton::Triangle},
            {Settings::NativeButton::Y, PadButton::Square},
            {Settings::NativeButton::Plus, PadButton::Options},
            {Settings::NativeButton::Minus, PadButton::Share},
            {Settings::NativeButton::DLeft, PadButton::Left},
            {Settings::NativeButton::DUp, PadButton::Up},
            {Settings::NativeButton::DRight, PadButton::Right},
            {Settings::NativeButton::DDown, PadButton::Down},
            {Settings::NativeButton::L, PadButton::L1},
            {Settings::NativeButton::R, PadButton::R1},
            {Settings::NativeButton::ZL, PadButton::L2},
            {Settings::NativeButton::ZR, PadButton::R2},
            {Settings::NativeButton::SLLeft, PadButton::L2},
            {Settings::NativeButton::SRLeft, PadButton::R2},
            {Settings::NativeButton::SLRight, PadButton::L2},
            {Settings::NativeButton::SRRight, PadButton::R2},
            {Settings::NativeButton::LStick, PadButton::L3},
            {Settings::NativeButton::RStick, PadButton::R3},
            {Settings::NativeButton::Home, PadButton::Home},
            {Settings::NativeButton::Screenshot, PadButton::TouchHardPress},
        };
    if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
        return {};
    }

    ButtonMapping mapping{};
    for (const auto& [switch_button, dsu_button] : switch_to_dsu_button) {
        Common::ParamPackage button_params{};
        button_params.Set("engine", GetEngineName());
        button_params.Set("guid", params.Get("guid", ""));
        button_params.Set("port", params.Get("port", 0));
        button_params.Set("pad", params.Get("pad", 0));
        button_params.Set("button", static_cast<int>(dsu_button));
        mapping.insert_or_assign(switch_button, std::move(button_params));
    }

    return mapping;
}

AnalogMapping UDPClient::GetAnalogMappingForDevice(const Common::ParamPackage& params) {
    if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
        return {};
    }

    AnalogMapping mapping = {};
    Common::ParamPackage left_analog_params;
    left_analog_params.Set("engine", GetEngineName());
    left_analog_params.Set("guid", params.Get("guid", ""));
    left_analog_params.Set("port", params.Get("port", 0));
    left_analog_params.Set("pad", params.Get("pad", 0));
    left_analog_params.Set("axis_x", static_cast<int>(PadAxes::LeftStickX));
    left_analog_params.Set("axis_y", static_cast<int>(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("guid", params.Get("guid", ""));
    right_analog_params.Set("port", params.Get("port", 0));
    right_analog_params.Set("pad", params.Get("pad", 0));
    right_analog_params.Set("axis_x", static_cast<int>(PadAxes::RightStickX));
    right_analog_params.Set("axis_y", static_cast<int>(PadAxes::RightStickY));
    mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params));
    return mapping;
}

MotionMapping UDPClient::GetMotionMappingForDevice(const Common::ParamPackage& params) {
    if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
        return {};
    }

    MotionMapping mapping = {};
    Common::ParamPackage left_motion_params;
    left_motion_params.Set("engine", GetEngineName());
    left_motion_params.Set("guid", params.Get("guid", ""));
    left_motion_params.Set("port", params.Get("port", 0));
    left_motion_params.Set("pad", params.Get("pad", 0));
    left_motion_params.Set("motion", 0);

    Common::ParamPackage right_motion_params;
    right_motion_params.Set("engine", GetEngineName());
    right_motion_params.Set("guid", params.Get("guid", ""));
    right_motion_params.Set("port", params.Get("port", 0));
    right_motion_params.Set("pad", params.Get("pad", 0));
    right_motion_params.Set("motion", 0);

    mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(left_motion_params));
    mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(right_motion_params));
    return mapping;
}

Common::Input::ButtonNames UDPClient::GetUIButtonName(const Common::ParamPackage& params) const {
    PadButton button = static_cast<PadButton>(params.Get("button", 0));
    switch (button) {
    case PadButton::Left:
        return Common::Input::ButtonNames::ButtonLeft;
    case PadButton::Right:
        return Common::Input::ButtonNames::ButtonRight;
    case PadButton::Down:
        return Common::Input::ButtonNames::ButtonDown;
    case PadButton::Up:
        return Common::Input::ButtonNames::ButtonUp;
    case PadButton::L1:
        return Common::Input::ButtonNames::L1;
    case PadButton::L2:
        return Common::Input::ButtonNames::L2;
    case PadButton::L3:
        return Common::Input::ButtonNames::L3;
    case PadButton::R1:
        return Common::Input::ButtonNames::R1;
    case PadButton::R2:
        return Common::Input::ButtonNames::R2;
    case PadButton::R3:
        return Common::Input::ButtonNames::R3;
    case PadButton::Circle:
        return Common::Input::ButtonNames::Circle;
    case PadButton::Cross:
        return Common::Input::ButtonNames::Cross;
    case PadButton::Square:
        return Common::Input::ButtonNames::Square;
    case PadButton::Triangle:
        return Common::Input::ButtonNames::Triangle;
    case PadButton::Share:
        return Common::Input::ButtonNames::Share;
    case PadButton::Options:
        return Common::Input::ButtonNames::Options;
    case PadButton::Home:
        return Common::Input::ButtonNames::Home;
    case PadButton::Touch1:
    case PadButton::Touch2:
    case PadButton::TouchHardPress:
        return Common::Input::ButtonNames::Touch;
    default:
        return Common::Input::ButtonNames::Undefined;
    }
}

Common::Input::ButtonNames UDPClient::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;
}

bool UDPClient::IsStickInverted(const Common::ParamPackage& params) {
    if (!params.Has("guid") || !params.Has("port") || !params.Has("pad")) {
        return false;
    }

    const auto x_axis = static_cast<PadAxes>(params.Get("axis_x", 0));
    const auto y_axis = static_cast<PadAxes>(params.Get("axis_y", 0));
    if (x_axis != PadAxes::LeftStickY && x_axis != PadAxes::RightStickY) {
        return false;
    }
    if (y_axis != PadAxes::LeftStickX && y_axis != PadAxes::RightStickX) {
        return false;
    }
    return true;
}

void TestCommunication(const std::string& host, u16 port,
                       const std::function<void()>& success_callback,
                       const std::function<void()>& failure_callback) {
    std::thread([=] {
        Common::Event success_event;
        SocketCallback callback{
            .version = [](Response::Version) {},
            .port_info = [](Response::PortInfo) {},
            .pad_data = [&](Response::PadData) { success_event.Set(); },
        };
        Socket socket{host, port, std::move(callback)};
        std::thread worker_thread{SocketLoop, &socket};
        const bool result =
            success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
        socket.Stop();
        worker_thread.join();
        if (result) {
            success_callback();
        } else {
            failure_callback();
        }
    }).detach();
}

CalibrationConfigurationJob::CalibrationConfigurationJob(
    const std::string& host, u16 port, std::function<void(Status)> status_callback,
    std::function<void(u16, u16, u16, u16)> data_callback) {

    std::thread([=, this] {
        u16 min_x{UINT16_MAX};
        u16 min_y{UINT16_MAX};
        u16 max_x{};
        u16 max_y{};

        Status current_status{Status::Initialized};
        SocketCallback callback{[](Response::Version) {}, [](Response::PortInfo) {},
                                [&](Response::PadData data) {
                                    constexpr u16 CALIBRATION_THRESHOLD = 100;

                                    if (current_status == Status::Initialized) {
                                        // Receiving data means the communication is ready now
                                        current_status = Status::Ready;
                                        status_callback(current_status);
                                    }
                                    if (data.touch[0].is_active == 0) {
                                        return;
                                    }
                                    LOG_DEBUG(Input, "Current touch: {} {}", data.touch[0].x,
                                              data.touch[0].y);
                                    min_x = std::min(min_x, static_cast<u16>(data.touch[0].x));
                                    min_y = std::min(min_y, static_cast<u16>(data.touch[0].y));
                                    if (current_status == Status::Ready) {
                                        // First touch - min data (min_x/min_y)
                                        current_status = Status::Stage1Completed;
                                        status_callback(current_status);
                                    }
                                    if (data.touch[0].x - min_x > CALIBRATION_THRESHOLD &&
                                        data.touch[0].y - min_y > CALIBRATION_THRESHOLD) {
                                        // Set the current position as max value and finishes
                                        // configuration
                                        max_x = data.touch[0].x;
                                        max_y = data.touch[0].y;
                                        current_status = Status::Completed;
                                        data_callback(min_x, min_y, max_x, max_y);
                                        status_callback(current_status);

                                        complete_event.Set();
                                    }
                                }};
        Socket socket{host, port, std::move(callback)};
        std::thread worker_thread{SocketLoop, &socket};
        complete_event.Wait();
        socket.Stop();
        worker_thread.join();
    }).detach();
}

CalibrationConfigurationJob::~CalibrationConfigurationJob() {
    Stop();
}

void CalibrationConfigurationJob::Stop() {
    complete_event.Set();
}

} // namespace InputCommon::CemuhookUDP


code:
// SPDX-FileCopyrightText: 2018 Citra Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <optional>

#include "common/common_types.h"
#include "common/thread.h"
#include "input_common/input_engine.h"

namespace InputCommon::CemuhookUDP {

class Socket;

namespace Response {
enum class Battery : u8;
struct PadData;
struct PortInfo;
struct TouchPad;
struct Version;
} // namespace Response

enum class PadTouch {
    Click,
    Undefined,
};

struct UDPPadStatus {
    std::string host{"127.0.0.1"};
    u16 port{26760};
    std::size_t pad_index{};
};

struct DeviceStatus {
    std::mutex update_mutex;

    // calibration data for scaling the device's touch area to 3ds
    struct CalibrationData {
        u16 min_x{};
        u16 min_y{};
        u16 max_x{};
        u16 max_y{};
    };
    std::optional<CalibrationData> touch_calibration;
};

/**
 * A button device factory representing a keyboard. It receives keyboard events and forward them
 * to all button devices it created.
 */
class UDPClient final : public InputEngine {
public:
    explicit UDPClient(std::string input_engine_);
    ~UDPClient() override;

    void ReloadSockets();

    /// Used for automapping features
    std::vector<Common::ParamPackage> 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;

    bool IsStickInverted(const Common::ParamPackage& params) override;

private:
    enum class PadButton {
        Undefined = 0x0000,
        Share = 0x0001,
        L3 = 0x0002,
        R3 = 0x0004,
        Options = 0x0008,
        Up = 0x0010,
        Right = 0x0020,
        Down = 0x0040,
        Left = 0x0080,
        L2 = 0x0100,
        R2 = 0x0200,
        L1 = 0x0400,
        R1 = 0x0800,
        Triangle = 0x1000,
        Circle = 0x2000,
        Cross = 0x4000,
        Square = 0x8000,
        Touch1 = 0x10000,
        Touch2 = 0x20000,
        Home = 0x40000,
        TouchHardPress = 0x80000,
    };

    enum class PadAxes : u8 {
        LeftStickX,
        LeftStickY,
        RightStickX,
        RightStickY,
        AnalogLeft,
        AnalogDown,
        AnalogRight,
        AnalogUp,
        AnalogSquare,
        AnalogCross,
        AnalogCircle,
        AnalogTriangle,
        AnalogR1,
        AnalogL1,
        AnalogR2,
        AnalogL3,
        AnalogR3,
        Touch1X,
        Touch1Y,
        Touch2X,
        Touch2Y,
        Undefined,
    };

    struct PadData {
        std::size_t pad_index{};
        bool connected{};
        DeviceStatus status;
        u64 packet_sequence{};

        std::chrono::time_point<std::chrono::steady_clock> last_update;
    };

    struct ClientConnection {
        ClientConnection();
        ~ClientConnection();
        Common::UUID uuid{"00000000-0000-0000-0000-00007F000001"};
        std::string host{"127.0.0.1"};
        u16 port{26760};
        s8 active{-1};
        std::unique_ptr<Socket> socket;
        std::thread thread;
    };

    // For shutting down, clear all data, join all threads, release usb
    void Reset();

    // Translates configuration to client number
    std::size_t GetClientNumber(std::string_view host, u16 port) const;

    // Translates UDP battery level to input engine battery level
    Common::Input::BatteryLevel GetBatteryLevel(Response::Battery battery) const;

    void OnVersion(Response::Version);
    void OnPortInfo(Response::PortInfo);
    void OnPadData(Response::PadData, std::size_t client);
    void StartCommunication(std::size_t client, const std::string& host, u16 port);
    PadIdentifier GetPadIdentifier(std::size_t pad_index) const;
    Common::UUID GetHostUUID(const std::string& host) const;

    Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;

    // Allocate clients for 8 udp servers
    static constexpr std::size_t MAX_UDP_CLIENTS = 8;
    static constexpr std::size_t PADS_PER_CLIENT = 4;
    std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
    std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
};

/// An async job allowing configuration of the touchpad calibration.
class CalibrationConfigurationJob {
public:
    enum class Status {
        Initialized,
        Ready,
        Stage1Completed,
        Completed,
    };
    /**
     * Constructs and starts the job with the specified parameter.
     *
     * @param status_callback Callback for job status updates
     * @param data_callback Called when calibration data is ready
     */
    explicit CalibrationConfigurationJob(const std::string& host, u16 port,
                                         std::function<void(Status)> status_callback,
                                         std::function<void(u16, u16, u16, u16)> data_callback);
    ~CalibrationConfigurationJob();
    void Stop();

private:
    Common::Event complete_event;
};

void TestCommunication(const std::string& host, u16 port,
                       const std::function<void()>& success_callback,
                       const std::function<void()>& failure_callback);

} // namespace InputCommon::CemuhookUDP

repo_name: litucks/torzu

license: mit


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#include <cstring>
#include <fmt/format.h>

#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/logging/log.h"
#include "common/settings.h"
#include "input_common/drivers/virtual_amiibo.h"

namespace InputCommon {
constexpr PadIdentifier identifier = {
    .guid = Common::UUID{},
    .port = 0,
    .pad = 0,
};

VirtualAmiibo::VirtualAmiibo(std::string input_engine_) : InputEngine(std::move(input_engine_)) {}

VirtualAmiibo::~VirtualAmiibo() = default;

Common::Input::DriverResult VirtualAmiibo::SetPollingMode(
    [[maybe_unused]] const PadIdentifier& identifier_,
    const Common::Input::PollingMode polling_mode_) {
    polling_mode = polling_mode_;

    switch (polling_mode) {
    case Common::Input::PollingMode::NFC:
        state = State::Initialized;
        return Common::Input::DriverResult::Success;
    default:
        if (state == State::TagNearby) {
            CloseAmiibo();
        }
        state = State::Disabled;
        return Common::Input::DriverResult::NotSupported;
    }
}

Common::Input::NfcState VirtualAmiibo::SupportsNfc(
    [[maybe_unused]] const PadIdentifier& identifier_) const {
    return Common::Input::NfcState::Success;
}
Common::Input::NfcState VirtualAmiibo::StartNfcPolling(const PadIdentifier& identifier_) {
    if (state != State::Initialized) {
        return Common::Input::NfcState::WrongDeviceState;
    }
    state = State::WaitingForAmiibo;
    return Common::Input::NfcState::Success;
}

Common::Input::NfcState VirtualAmiibo::StopNfcPolling(const PadIdentifier& identifier_) {
    if (state == State::Disabled) {
        return Common::Input::NfcState::WrongDeviceState;
    }
    if (state == State::TagNearby) {
        CloseAmiibo();
    }
    state = State::Initialized;
    return Common::Input::NfcState::Success;
}

Common::Input::NfcState VirtualAmiibo::ReadAmiiboData(const PadIdentifier& identifier_,
                                                      std::vector<u8>& out_data) {
    if (state != State::TagNearby) {
        return Common::Input::NfcState::WrongDeviceState;
    }

    if (status.tag_type != 1U << 1) {
        return Common::Input::NfcState::InvalidTagType;
    }

    out_data.resize(nfc_data.size());
    memcpy(out_data.data(), nfc_data.data(), nfc_data.size());
    return Common::Input::NfcState::Success;
}

Common::Input::NfcState VirtualAmiibo::WriteNfcData(
    [[maybe_unused]] const PadIdentifier& identifier_, const std::vector<u8>& data) {
    const Common::FS::IOFile nfc_file{file_path, Common::FS::FileAccessMode::ReadWrite,
                                      Common::FS::FileType::BinaryFile};

    if (!nfc_file.IsOpen()) {
        LOG_ERROR(Core, "Amiibo is already on use");
        return Common::Input::NfcState::WriteFailed;
    }

    if (!nfc_file.Write(data)) {
        LOG_ERROR(Service_NFP, "Error writing to file");
        return Common::Input::NfcState::WriteFailed;
    }

    nfc_data = data;

    return Common::Input::NfcState::Success;
}

Common::Input::NfcState VirtualAmiibo::ReadMifareData(const PadIdentifier& identifier_,
                                                      const Common::Input::MifareRequest& request,
                                                      Common::Input::MifareRequest& out_data) {
    if (state != State::TagNearby) {
        return Common::Input::NfcState::WrongDeviceState;
    }

    if (status.tag_type != 1U << 6) {
        return Common::Input::NfcState::InvalidTagType;
    }

    for (std::size_t i = 0; i < request.data.size(); i++) {
        if (request.data[i].command == 0) {
            continue;
        }
        out_data.data[i].command = request.data[i].command;
        out_data.data[i].sector = request.data[i].sector;

        const std::size_t sector_index =
            request.data[i].sector * sizeof(Common::Input::MifareData::data);

        if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) {
            return Common::Input::NfcState::WriteFailed;
        }

        // Ignore the sector key as we don't support it
        memcpy(out_data.data[i].data.data(), nfc_data.data() + sector_index,
               sizeof(Common::Input::MifareData::data));
    }

    return Common::Input::NfcState::Success;
}

Common::Input::NfcState VirtualAmiibo::WriteMifareData(
    const PadIdentifier& identifier_, const Common::Input::MifareRequest& request) {
    if (state != State::TagNearby) {
        return Common::Input::NfcState::WrongDeviceState;
    }

    if (status.tag_type != 1U << 6) {
        return Common::Input::NfcState::InvalidTagType;
    }

    for (std::size_t i = 0; i < request.data.size(); i++) {
        if (request.data[i].command == 0) {
            continue;
        }

        const std::size_t sector_index =
            request.data[i].sector * sizeof(Common::Input::MifareData::data);

        if (nfc_data.size() < sector_index + sizeof(Common::Input::MifareData::data)) {
            return Common::Input::NfcState::WriteFailed;
        }

        // Ignore the sector key as we don't support it
        memcpy(nfc_data.data() + sector_index, request.data[i].data.data(),
               sizeof(Common::Input::MifareData::data));
    }

    return Common::Input::NfcState::Success;
}

VirtualAmiibo::State VirtualAmiibo::GetCurrentState() const {
    return state;
}

VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(const std::string& filename) {
    const Common::FS::IOFile nfc_file{filename, Common::FS::FileAccessMode::Read,
                                      Common::FS::FileType::BinaryFile};
    std::vector<u8> data{};

    if (!nfc_file.IsOpen()) {
        return Info::UnableToLoad;
    }

    switch (nfc_file.GetSize()) {
    case AmiiboSize:
    case AmiiboSizeWithoutPassword:
    case AmiiboSizeWithSignature:
        data.resize(AmiiboSize);
        if (nfc_file.Read(data) < AmiiboSizeWithoutPassword) {
            return Info::NotAnAmiibo;
        }
        break;
    case MifareSize:
        data.resize(MifareSize);
        if (nfc_file.Read(data) < MifareSize) {
            return Info::NotAnAmiibo;
        }
        break;
    default:
        return Info::NotAnAmiibo;
    }

    file_path = filename;
    return LoadAmiibo(data);
}

VirtualAmiibo::Info VirtualAmiibo::LoadAmiibo(std::span<u8> data) {
    if (state != State::WaitingForAmiibo) {
        return Info::WrongDeviceState;
    }

    switch (data.size_bytes()) {
    case AmiiboSize:
    case AmiiboSizeWithoutPassword:
    case AmiiboSizeWithSignature:
        nfc_data.resize(AmiiboSize);
        status.tag_type = 1U << 1;
        status.uuid_length = 7;
        break;
    case MifareSize:
        nfc_data.resize(MifareSize);
        status.tag_type = 1U << 6;
        status.uuid_length = 4;
        break;
    default:
        return Info::NotAnAmiibo;
    }

    status.uuid = {};
    status.protocol = 1;
    state = State::TagNearby;
    status.state = Common::Input::NfcState::NewAmiibo,
    memcpy(nfc_data.data(), data.data(), data.size_bytes());
    memcpy(status.uuid.data(), nfc_data.data(), status.uuid_length);
    SetNfc(identifier, status);
    return Info::Success;
}

VirtualAmiibo::Info VirtualAmiibo::ReloadAmiibo() {
    if (state == State::TagNearby) {
        SetNfc(identifier, status);
        return Info::Success;
    }

    return LoadAmiibo(file_path);
}

VirtualAmiibo::Info VirtualAmiibo::CloseAmiibo() {
    if (state != State::TagNearby) {
        return Info::Success;
    }

    state = State::WaitingForAmiibo;
    status.state = Common::Input::NfcState::AmiiboRemoved;
    SetNfc(identifier, status);
    status.tag_type = 0;
    return Info::Success;
}

std::string VirtualAmiibo::GetLastFilePath() const {
    return file_path;
}

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later

#pragma once

#include <array>
#include <span>
#include <string>
#include <vector>

#include "common/common_types.h"
#include "input_common/input_engine.h"

namespace Common::FS {
class IOFile;
}

namespace InputCommon {

class VirtualAmiibo final : public InputEngine {
public:
    enum class State {
        Disabled,
        Initialized,
        WaitingForAmiibo,
        TagNearby,
    };

    enum class Info {
        Success,
        UnableToLoad,
        NotAnAmiibo,
        WrongDeviceState,
        Unknown,
    };

    explicit VirtualAmiibo(std::string input_engine_);
    ~VirtualAmiibo() override;

    // Sets polling mode to a controller
    Common::Input::DriverResult SetPollingMode(
        const PadIdentifier& identifier_, const Common::Input::PollingMode polling_mode_) override;

    Common::Input::NfcState SupportsNfc(const PadIdentifier& identifier_) const override;
    Common::Input::NfcState StartNfcPolling(const PadIdentifier& identifier_) override;
    Common::Input::NfcState StopNfcPolling(const PadIdentifier& identifier_) override;
    Common::Input::NfcState ReadAmiiboData(const PadIdentifier& identifier_,
                                           std::vector<u8>& out_data) override;
    Common::Input::NfcState WriteNfcData(const PadIdentifier& identifier_,
                                         const std::vector<u8>& data) override;
    Common::Input::NfcState ReadMifareData(const PadIdentifier& identifier_,
                                           const Common::Input::MifareRequest& data,
                                           Common::Input::MifareRequest& out_data) override;
    Common::Input::NfcState WriteMifareData(const PadIdentifier& identifier_,
                                            const Common::Input::MifareRequest& data) override;

    State GetCurrentState() const;

    Info LoadAmiibo(const std::string& amiibo_file);
    Info LoadAmiibo(std::span<u8> data);
    Info ReloadAmiibo();
    Info CloseAmiibo();

    std::string GetLastFilePath() const;

private:
    static constexpr std::size_t AmiiboSize = 0x21C;
    static constexpr std::size_t AmiiboSizeWithoutPassword = AmiiboSize - 0x8;
    static constexpr std::size_t AmiiboSizeWithSignature = AmiiboSize + 0x20;
    static constexpr std::size_t MifareSize = 0x400;

    std::string file_path{};
    State state{State::Disabled};
    std::vector<u8> nfc_data;
    Common::Input::NfcStatus status;
    Common::Input::PollingMode polling_mode{Common::Input::PollingMode::Passive};
};
} // namespace InputCommon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include "input_common/drivers/virtual_gamepad.h"

namespace InputCommon {
constexpr std::size_t PlayerIndexCount = 10;

VirtualGamepad::VirtualGamepad(std::string input_engine_) : InputEngine(std::move(input_engine_)) {
    for (std::size_t i = 0; i < PlayerIndexCount; i++) {
        PreSetController(GetIdentifier(i));
    }
}

void VirtualGamepad::SetButtonState(std::size_t player_index, int button_id, bool value) {
    if (player_index > PlayerIndexCount) {
        return;
    }
    const auto identifier = GetIdentifier(player_index);
    SetButton(identifier, button_id, value);
}

void VirtualGamepad::SetButtonState(std::size_t player_index, VirtualButton button_id, bool value) {
    SetButtonState(player_index, static_cast<int>(button_id), value);
}

void VirtualGamepad::SetStickPosition(std::size_t player_index, int axis_id, float x_value,
                                      float y_value) {
    if (player_index > PlayerIndexCount) {
        return;
    }
    const auto identifier = GetIdentifier(player_index);
    SetAxis(identifier, axis_id * 2, x_value);
    SetAxis(identifier, (axis_id * 2) + 1, y_value);
}

void VirtualGamepad::SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
                                      float y_value) {
    SetStickPosition(player_index, static_cast<int>(axis_id), x_value, y_value);
}

void VirtualGamepad::SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x,
                                    float gyro_y, float gyro_z, float accel_x, float accel_y,
                                    float accel_z) {
    const auto identifier = GetIdentifier(player_index);
    const BasicMotion motion_data{
        .gyro_x = gyro_x,
        .gyro_y = gyro_y,
        .gyro_z = gyro_z,
        .accel_x = accel_x,
        .accel_y = accel_y,
        .accel_z = accel_z,
        .delta_timestamp = delta_timestamp,
    };
    SetMotion(identifier, 0, motion_data);
}

void VirtualGamepad::ResetControllers() {
    for (std::size_t i = 0; i < PlayerIndexCount; i++) {
        SetStickPosition(i, VirtualStick::Left, 0.0f, 0.0f);
        SetStickPosition(i, VirtualStick::Right, 0.0f, 0.0f);

        SetButtonState(i, VirtualButton::ButtonA, false);
        SetButtonState(i, VirtualButton::ButtonB, false);
        SetButtonState(i, VirtualButton::ButtonX, false);
        SetButtonState(i, VirtualButton::ButtonY, false);
        SetButtonState(i, VirtualButton::StickL, false);
        SetButtonState(i, VirtualButton::StickR, false);
        SetButtonState(i, VirtualButton::TriggerL, false);
        SetButtonState(i, VirtualButton::TriggerR, false);
        SetButtonState(i, VirtualButton::TriggerZL, false);
        SetButtonState(i, VirtualButton::TriggerZR, false);
        SetButtonState(i, VirtualButton::ButtonPlus, false);
        SetButtonState(i, VirtualButton::ButtonMinus, false);
        SetButtonState(i, VirtualButton::ButtonLeft, false);
        SetButtonState(i, VirtualButton::ButtonUp, false);
        SetButtonState(i, VirtualButton::ButtonRight, false);
        SetButtonState(i, VirtualButton::ButtonDown, false);
        SetButtonState(i, VirtualButton::ButtonSL, false);
        SetButtonState(i, VirtualButton::ButtonSR, false);
        SetButtonState(i, VirtualButton::ButtonHome, false);
        SetButtonState(i, VirtualButton::ButtonCapture, false);
    }
}

PadIdentifier VirtualGamepad::GetIdentifier(std::size_t player_index) const {
    return {
        .guid = Common::UUID{},
        .port = player_index,
        .pad = 0,
    };
}

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include "input_common/input_engine.h"

namespace InputCommon {

/**
 * A virtual controller that is always assigned to the game input
 */
class VirtualGamepad final : public InputEngine {
public:
    enum class VirtualButton {
        ButtonA,
        ButtonB,
        ButtonX,
        ButtonY,
        StickL,
        StickR,
        TriggerL,
        TriggerR,
        TriggerZL,
        TriggerZR,
        ButtonPlus,
        ButtonMinus,
        ButtonLeft,
        ButtonUp,
        ButtonRight,
        ButtonDown,
        ButtonSL,
        ButtonSR,
        ButtonHome,
        ButtonCapture,
    };

    enum class VirtualStick {
        Left = 0,
        Right = 1,
    };

    explicit VirtualGamepad(std::string input_engine_);

    /**
     * Sets the status of all buttons bound with the key to pressed
     * @param player_index the player number that will take this action
     * @param button_id the id of the button
     * @param value indicates if the button is pressed or not
     */
    void SetButtonState(std::size_t player_index, int button_id, bool value);
    void SetButtonState(std::size_t player_index, VirtualButton button_id, bool value);

    /**
     * Sets the status of a stick to a specific player index
     * @param player_index the player number that will take this action
     * @param axis_id the id of the axis to move
     * @param x_value the position of the stick in the x axis
     * @param y_value the position of the stick in the y axis
     */
    void SetStickPosition(std::size_t player_index, int axis_id, float x_value, float y_value);
    void SetStickPosition(std::size_t player_index, VirtualStick axis_id, float x_value,
                          float y_value);

    /**
     * Sets the status of the motion sensor to a specific player index
     * @param player_index the player number that will take this action
     * @param delta_timestamp time passed since last reading
     * @param gyro_x,gyro_y,gyro_z the gyro sensor readings
     * @param accel_x,accel_y,accel_z the accelerometer reading
     */
    void SetMotionState(std::size_t player_index, u64 delta_timestamp, float gyro_x, float gyro_y,
                        float gyro_z, float accel_x, float accel_y, float accel_z);

    /// Restores all inputs into the neutral position
    void ResetControllers();

private:
    /// Returns the correct identifier corresponding to the player index
    PadIdentifier GetIdentifier(std::size_t player_index) const;
};

} // namespace InputCommon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#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;

    // Reset Counters
    error_counter = 0;
    hidapi_handle->packet_counter = 0;

    // Reset external device status
    starlink_connected = false;
    ring_connected = false;
    amiibo_detected = false;

    // Set HW default configuration
    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;

    // Initialize HW Protocols
    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);

    // Get fixed joycon info
    if (generic_protocol->GetVersionNumber(version) != Common::Input::DriverResult::Success) {
        // If this command fails the device doesn't accept configuration commands
        input_only_device = true;
    }

    if (!input_only_device) {
        generic_protocol->SetLowPowerMode(false);
        generic_protocol->GetColor(color);
        if (handle_device_type == ControllerType::Pro) {
            // Some 3rd party controllers aren't pro controllers
            generic_protocol->GetControllerType(device_type);
        } else {
            device_type = handle_device_type;
        }
        generic_protocol->GetSerialNumber(serial_number);
    }

    supported_features = GetSupportedFeatures();

    // Get Calibration data
    calibration_protocol->GetLeftJoyStickCalibration(left_stick_calibration);
    calibration_protocol->GetRightJoyStickCalibration(right_stick_calibration);
    calibration_protocol->GetImuCalibration(motion_calibration);

    // Set led status
    generic_protocol->SetLedBlinkPattern(static_cast<u8>(1 + port));

    // Apply HW configuration
    SetPollingMode();

    // Initialize joycon poller
    joycon_poller = std::make_unique<JoyconPoller>(device_type, left_stick_calibration,
                                                   right_stick_calibration, motion_calibration);

    // Start polling 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 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;

    // Max update rate is 5ms, ensure we are always able to read a bit faster
    constexpr int ThreadDelay = 3;
    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);
        }

        if (!vibration_queue.Empty()) {
            VibrationValue vibration_value;
            vibration_queue.Pop(vibration_value);
            last_vibration_result = rumble_protocol->SendVibration(vibration_value);
        }

        // We can't keep up with vibrations. Start skipping.
        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]);

    // Packages can be a little bit inconsistent. Average the delta time to provide a smoother
    // motion experience
    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,
    };

    // TODO: Remove this when calibration is properly loaded and not calculated
    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");
    }

    // Default Mode
    const auto result = generic_protocol->EnableActiveMode();
    if (result != Common::Input::DriverResult::Success) {
        LOG_ERROR(Input, "Error enabling active mode");
    }
    // Switch calls this function after 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;
    }
    // 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;
}

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;
}

} // namespace InputCommon::Joycon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#pragma once

#include <atomic>
#include <functional>
#include <mutex>
#include <span>
#include <thread>

#include "common/threadsafe_queue.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"

namespace Common::Input {
enum class DriverResult;
}

namespace InputCommon::Joycon {
class CalibrationProtocol;
class GenericProtocol;
class IrsProtocol;
class NfcProtocol;
class JoyconPoller;
class RingConProtocol;
class RumbleProtocol;

class JoyconDriver final {
public:
    explicit JoyconDriver(std::size_t port_);

    ~JoyconDriver();

    Common::Input::DriverResult RequestDeviceAccess(SDL_hid_device_info* device_info);
    Common::Input::DriverResult InitializeDevice();
    void Stop();

    bool IsConnected() const;
    bool IsVibrationEnabled() const;

    FirmwareVersion GetDeviceVersion() const;
    Color GetDeviceColor() const;
    std::size_t GetDevicePort() const;
    ControllerType GetDeviceType() const;
    ControllerType GetHandleDeviceType() const;
    SerialNumber GetSerialNumber() const;
    SerialNumber GetHandleSerialNumber() const;

    Common::Input::DriverResult SetVibration(const VibrationValue& vibration);
    Common::Input::DriverResult SetLedConfig(u8 led_pattern);
    Common::Input::DriverResult SetIrsConfig(IrsMode mode_, IrsResolution format_);
    Common::Input::DriverResult SetPassiveMode();
    Common::Input::DriverResult SetActiveMode();
    Common::Input::DriverResult SetIrMode();
    Common::Input::DriverResult SetNfcMode();
    Common::Input::DriverResult SetRingConMode();
    Common::Input::DriverResult StartNfcPolling();
    Common::Input::DriverResult StopNfcPolling();
    Common::Input::DriverResult ReadAmiiboData(std::vector<u8>& out_data);
    Common::Input::DriverResult WriteNfcData(std::span<const u8> data);
    Common::Input::DriverResult ReadMifareData(std::span<const MifareReadChunk> request,
                                               std::span<MifareReadData> out_data);
    Common::Input::DriverResult WriteMifareData(std::span<const MifareWriteChunk> request);

    void SetCallbacks(const JoyconCallbacks& callbacks);

    // Returns device type from hidapi handle
    static Common::Input::DriverResult GetDeviceType(SDL_hid_device_info* device_info,
                                                     ControllerType& controller_type);

    // Returns serial number from hidapi handle
    static Common::Input::DriverResult GetSerialNumber(SDL_hid_device_info* device_info,
                                                       SerialNumber& serial_number);

private:
    struct SupportedFeatures {
        bool passive{};
        bool hidbus{};
        bool irs{};
        bool motion{};
        bool nfc{};
        bool vibration{};
    };

    /// Main thread, actively request new data from the handle
    void InputThread(std::stop_token stop_token);

    /// Called every time a valid package arrives
    void OnNewData(std::span<u8> buffer);

    /// Updates device configuration to enable or disable features
    Common::Input::DriverResult SetPollingMode();

    /// Returns true if input thread is valid and doesn't need to be stopped
    bool IsInputThreadValid() const;

    /// Returns true if the data should be interpreted. Otherwise the error counter is incremented
    bool IsPayloadCorrect(int status, std::span<const u8> buffer);

    /// Returns a list of supported features that can be enabled on this device
    SupportedFeatures GetSupportedFeatures();

    // Protocol Features
    std::unique_ptr<CalibrationProtocol> calibration_protocol;
    std::unique_ptr<GenericProtocol> generic_protocol;
    std::unique_ptr<IrsProtocol> irs_protocol;
    std::unique_ptr<NfcProtocol> nfc_protocol;
    std::unique_ptr<JoyconPoller> joycon_poller;
    std::unique_ptr<RingConProtocol> ring_protocol;
    std::unique_ptr<RumbleProtocol> rumble_protocol;

    // Connection status
    std::atomic<bool> is_connected{};
    u64 delta_time;
    std::size_t error_counter{};
    std::shared_ptr<JoyconHandle> hidapi_handle;
    std::chrono::time_point<std::chrono::steady_clock> last_update;

    // External device status
    bool starlink_connected{};
    bool ring_connected{};
    bool amiibo_detected{};
    bool is_ring_disabled_by_irs{};

    // Hardware configuration
    u8 leds{};
    ReportMode mode{};
    bool input_only_device{};
    bool passive_enabled{};   // Low power mode, Ideal for multiple controllers at the same time
    bool hidbus_enabled{};    // External device support
    bool irs_enabled{};       // Infrared camera input
    bool motion_enabled{};    // Enables motion input
    bool nfc_enabled{};       // Enables Amiibo detection
    bool vibration_enabled{}; // Allows vibrations

    // Calibration data
    GyroSensitivity gyro_sensitivity{};
    GyroPerformance gyro_performance{};
    AccelerometerSensitivity accelerometer_sensitivity{};
    AccelerometerPerformance accelerometer_performance{};
    JoyStickCalibration left_stick_calibration{};
    JoyStickCalibration right_stick_calibration{};
    MotionCalibration motion_calibration{};
    RingCalibration ring_calibration{};

    // Fixed joycon info
    FirmwareVersion version{};
    Color color{};
    std::size_t port{};
    ControllerType device_type{};        // Device type reported by controller
    ControllerType handle_device_type{}; // Device type reported by hidapi
    SerialNumber serial_number{};        // Serial number reported by controller
    SerialNumber handle_serial_number{}; // Serial number type reported by hidapi
    SupportedFeatures supported_features{};

    /// Queue of vibration request to controllers
    Common::Input::DriverResult last_vibration_result{Common::Input::DriverResult::Success};
    Common::SPSCQueue<VibrationValue> vibration_queue;

    // Thread related
    mutable std::mutex mutex;
    std::jthread input_thread;
    bool input_thread_running{};
    bool disable_input_thread{};
};

} // namespace InputCommon::Joycon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

#include <cstring>

#include "common/input.h"
#include "input_common/helpers/joycon_protocol/calibration.h"
#include "input_common/helpers/joycon_protocol/joycon_types.h"

namespace InputCommon::Joycon {

CalibrationProtocol::CalibrationProtocol(std::shared_ptr<JoyconHandle> handle)
    : JoyconCommonProtocol(std::move(handle)) {}

Common::Input::DriverResult CalibrationProtocol::GetLeftJoyStickCalibration(
    JoyStickCalibration& calibration) {
    ScopedSetBlocking sb(this);
    Common::Input::DriverResult result{Common::Input::DriverResult::Success};
    JoystickLeftSpiCalibration spi_calibration{};
    bool has_user_calibration = false;
    calibration = {};

    if (result == Common::Input::DriverResult::Success) {
        result = HasUserCalibration(SpiAddress::USER_LEFT_MAGIC, has_user_calibration);
    }

    // Read User defined calibration
    if (result == Common::Input::DriverResult::Success && has_user_calibration) {
        result = ReadSPI(SpiAddress::USER_LEFT_DATA, spi_calibration);
    }

    // Read Factory calibration
    if (result == Common::Input::DriverResult::Success && !has_user_calibration) {
        result = ReadSPI(SpiAddress::FACT_LEFT_DATA, spi_calibration);
    }

    if (result == Common::Input::DriverResult::Success) {
        calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center);
        calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center);
        calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min);
        calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min);
        calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max);
        calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max);
    }

    // Set a valid default calibration if data is missing
    ValidateCalibration(calibration);

    return result;
}

Common::Input::DriverResult CalibrationProtocol::GetRightJoyStickCalibration(
    JoyStickCalibration& calibration) {
    ScopedSetBlocking sb(this);
    Common::Input::DriverResult result{Common::Input::DriverResult::Success};
    JoystickRightSpiCalibration spi_calibration{};
    bool has_user_calibration = false;
    calibration = {};

    if (result == Common::Input::DriverResult::Success) {
        result = HasUserCalibration(SpiAddress::USER_RIGHT_MAGIC, has_user_calibration);
    }

    // Read User defined calibration
    if (result == Common::Input::DriverResult::Success && has_user_calibration) {
        result = ReadSPI(SpiAddress::USER_RIGHT_DATA, spi_calibration);
    }

    // Read Factory calibration
    if (result == Common::Input::DriverResult::Success && !has_user_calibration) {
        result = ReadSPI(SpiAddress::FACT_RIGHT_DATA, spi_calibration);
    }

    if (result == Common::Input::DriverResult::Success) {
        calibration.x.center = GetXAxisCalibrationValue(spi_calibration.center);
        calibration.y.center = GetYAxisCalibrationValue(spi_calibration.center);
        calibration.x.min = GetXAxisCalibrationValue(spi_calibration.min);
        calibration.y.min = GetYAxisCalibrationValue(spi_calibration.min);
        calibration.x.max = GetXAxisCalibrationValue(spi_calibration.max);
        calibration.y.max = GetYAxisCalibrationValue(spi_calibration.max);
    }

    // Set a valid default calibration if data is missing
    ValidateCalibration(calibration);

    return result;
}

Common::Input::DriverResult CalibrationProtocol::GetImuCalibration(MotionCalibration& calibration) {
    ScopedSetBlocking sb(this);
    Common::Input::DriverResult result{Common::Input::DriverResult::Success};
    ImuSpiCalibration spi_calibration{};
    bool has_user_calibration = false;
    calibration = {};

    if (result == Common::Input::DriverResult::Success) {
        result = HasUserCalibration(SpiAddress::USER_IMU_MAGIC, has_user_calibration);
    }

    // Read User defined calibration
    if (result == Common::Input::DriverResult::Success && has_user_calibration) {
        result = ReadSPI(SpiAddress::USER_IMU_DATA, spi_calibration);
    }

    // Read Factory calibration
    if (result == Common::Input::DriverResult::Success && !has_user_calibration) {
        result = ReadSPI(SpiAddress::FACT_IMU_DATA, spi_calibration);
    }

    if (result == Common::Input::DriverResult::Success) {
        calibration.accelerometer[0].offset = spi_calibration.accelerometer_offset[0];
        calibration.accelerometer[1].offset = spi_calibration.accelerometer_offset[1];
        calibration.accelerometer[2].offset = spi_calibration.accelerometer_offset[2];

        calibration.accelerometer[0].scale = spi_calibration.accelerometer_scale[0];
        calibration.accelerometer[1].scale = spi_calibration.accelerometer_scale[1];
        calibration.accelerometer[2].scale = spi_calibration.accelerometer_scale[2];

        calibration.gyro[0].offset = spi_calibration.gyroscope_offset[0];
        calibration.gyro[1].offset = spi_calibration.gyroscope_offset[1];
        calibration.gyro[2].offset = spi_calibration.gyroscope_offset[2];

        calibration.gyro[0].scale = spi_calibration.gyroscope_scale[0];
        calibration.gyro[1].scale = spi_calibration.gyroscope_scale[1];
        calibration.gyro[2].scale = spi_calibration.gyroscope_scale[2];
    }

    ValidateCalibration(calibration);

    return result;
}

Common::Input::DriverResult CalibrationProtocol::GetRingCalibration(RingCalibration& calibration,
                                                                    s16 current_value) {
    constexpr s16 DefaultRingRange{800};

    // TODO: Get default calibration form ring itself
    if (ring_data_max == 0 && ring_data_min == 0) {
        ring_data_max = current_value + DefaultRingRange;
        ring_data_min = current_value - DefaultRingRange;
        ring_data_default = current_value;
    }
    ring_data_max = std::max(ring_data_max, current_value);
    ring_data_min = std::min(ring_data_min, current_value);
    calibration = {
        .default_value = ring_data_default,
        .max_value = ring_data_max,
        .min_value = ring_data_min,
    };
    return Common::Input::DriverResult::Success;
}

Common::Input::DriverResult CalibrationProtocol::HasUserCalibration(SpiAddress address,
                                                                    bool& has_user_calibration) {
    MagicSpiCalibration spi_magic{};
    const Common::Input::DriverResult result{ReadSPI(address, spi_magic)};
    has_user_calibration = false;
    if (result == Common::Input::DriverResult::Success) {
        has_user_calibration = spi_magic.first == CalibrationMagic::USR_MAGIC_0 &&
                               spi_magic.second == CalibrationMagic::USR_MAGIC_1;
    }
    return result;
}

u16 CalibrationProtocol::GetXAxisCalibrationValue(std::span<u8> block) const {
    return static_cast<u16>(((block[1] & 0x0F) << 8) | block[0]);
}

u16 CalibrationProtocol::GetYAxisCalibrationValue(std::span<u8> block) const {
    return static_cast<u16>((block[2] << 4) | (block[1] >> 4));
}

void CalibrationProtocol::ValidateCalibration(JoyStickCalibration& calibration) {
    constexpr u16 DefaultStickCenter{0x800};
    constexpr u16 DefaultStickRange{0x6cc};

    calibration.x.center = ValidateValue(calibration.x.center, DefaultStickCenter);
    calibration.x.max = ValidateValue(calibration.x.max, DefaultStickRange);
    calibration.x.min = ValidateValue(calibration.x.min, DefaultStickRange);

    calibration.y.center = ValidateValue(calibration.y.center, DefaultStickCenter);
    calibration.y.max = ValidateValue(calibration.y.max, DefaultStickRange);
    calibration.y.min = ValidateValue(calibration.y.min, DefaultStickRange);
}

void CalibrationProtocol::ValidateCalibration(MotionCalibration& calibration) {
    constexpr s16 DefaultAccelerometerScale{0x4000};
    constexpr s16 DefaultGyroScale{0x3be7};
    constexpr s16 DefaultOffset{0};

    for (auto& sensor : calibration.accelerometer) {
        sensor.scale = ValidateValue(sensor.scale, DefaultAccelerometerScale);
        sensor.offset = ValidateValue(sensor.offset, DefaultOffset);
    }
    for (auto& sensor : calibration.gyro) {
        sensor.scale = ValidateValue(sensor.scale, DefaultGyroScale);
        sensor.offset = ValidateValue(sensor.offset, DefaultOffset);
    }
}

u16 CalibrationProtocol::ValidateValue(u16 value, u16 default_value) const {
    if (value == 0) {
        return default_value;
    }
    if (value == 0xFFF) {
        return default_value;
    }
    return value;
}

s16 CalibrationProtocol::ValidateValue(s16 value, s16 default_value) const {
    if (value == 0) {
        return default_value;
    }
    if (value == 0xFFF) {
        return default_value;
    }
    return value;
}

} // namespace InputCommon::Joycon


code:
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later

// Based on dkms-hid-nintendo implementation, CTCaer joycon toolkit and dekuNukem reverse
// engineering https://github.com/nicman23/dkms-hid-nintendo/blob/master/src/hid-nintendo.c
// https://github.com/CTCaer/jc_toolkit
// https://github.com/dekuNukem/Nintendo_Switch_Reverse_Engineering

#pragma once

#include <vector>

#include "input_common/helpers/joycon_protocol/common_protocol.h"

namespace Common::Input {
enum class DriverResult;
}

namespace InputCommon::Joycon {
struct JoyStickCalibration;
struct IMUCalibration;
struct JoyconHandle;
} // namespace InputCommon::Joycon

namespace InputCommon::Joycon {

/// Driver functions related to retrieving calibration data from the device
class CalibrationProtocol final : private JoyconCommonProtocol {
public:
    explicit CalibrationProtocol(std::shared_ptr<JoyconHandle> handle);

    /**
     * Sends a request to obtain the left stick calibration from memory
     * @param is_factory_calibration if true factory values will be returned
     * @returns JoyStickCalibration of the left joystick
     */
    Common::Input::DriverResult GetLeftJoyStickCalibration(JoyStickCalibration& calibration);

    /**
     * Sends a request to obtain the right stick calibration from memory
     * @param is_factory_calibration if true factory values will be returned
     * @returns JoyStickCalibration of the right joystick
     */
    Common::Input::DriverResult GetRightJoyStickCalibration(JoyStickCalibration& calibration);

    /**
     * Sends a request to obtain the motion calibration from memory
     * @returns ImuCalibration of the motion sensor
     */
    Common::Input::DriverResult GetImuCalibration(MotionCalibration& calibration);

    /**
     * Calculates on run time the proper calibration of the ring controller
     * @returns RingCalibration of the ring sensor
     */
    Common::Input::DriverResult GetRingCalibration(RingCalibration& calibration, s16 current_value);

private:
    /// Returns true if the specified address corresponds to the magic value of user calibration
    Common::Input::DriverResult HasUserCalibration(SpiAddress address, bool& has_user_calibration);

    /// Converts a raw calibration block to an u16 value containing the x axis value
    u16 GetXAxisCalibrationValue(std::span<u8> block) const;

    /// Converts a raw calibration block to an u16 value containing the y axis value
    u16 GetYAxisCalibrationValue(std::span<u8> block) const;

    /// Ensures that all joystick calibration values are set
    void ValidateCalibration(JoyStickCalibration& calibration);

    /// Ensures that all motion calibration values are set
    void ValidateCalibration(MotionCalibration& calibration);

    /// Returns the default value if the value is either zero or 0xFFF
    u16 ValidateValue(u16 value, u16 default_value) const;

    /// Returns the default value if the value is either zero or 0xFFF
    s16 ValidateValue(s16 value, s16 default_value) const;

    s16 ring_data_max = 0;
    s16 ring_data_default = 0;
    s16 ring_data_min = 0;
};

} // namespace InputCommon::Joycon