diff options
Diffstat (limited to '')
54 files changed, 5950 insertions, 5884 deletions
diff --git a/src/input_common/motion_input.cpp b/src/core/hid/motion_input.cpp index 1c9d561c0..c25fea966 100644 --- a/src/input_common/motion_input.cpp +++ b/src/core/hid/motion_input.cpp @@ -2,13 +2,21 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included -#include <random> #include "common/math_util.h" -#include "input_common/motion_input.h" +#include "core/hid/motion_input.h" -namespace InputCommon { +namespace Core::HID { -MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) : kp(new_kp), ki(new_ki), kd(new_kd) {} +MotionInput::MotionInput() { + // Initialize PID constants with default values + SetPID(0.3f, 0.005f, 0.0f); +} + +void MotionInput::SetPID(f32 new_kp, f32 new_ki, f32 new_kd) { + kp = new_kp; + ki = new_ki; + kd = new_kd; +} void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { accel = acceleration; @@ -65,6 +73,8 @@ void MotionInput::UpdateRotation(u64 elapsed_time) { rotations += gyro * sample_period; } +// Based on Madgwick's implementation of Mayhony's AHRS algorithm. +// https://github.com/xioTechnologies/Open-Source-AHRS-With-x-IMU/blob/master/x-IMU%20IMU%20and%20AHRS%20Algorithms/x-IMU%20IMU%20and%20AHRS%20Algorithms/AHRS/MahonyAHRS.cs void MotionInput::UpdateOrientation(u64 elapsed_time) { if (!IsCalibrated(0.1f)) { ResetOrientation(); @@ -190,43 +200,6 @@ Common::Vec3f MotionInput::GetRotations() const { return rotations; } -Input::MotionStatus MotionInput::GetMotion() const { - const Common::Vec3f gyroscope = GetGyroscope(); - const Common::Vec3f accelerometer = GetAcceleration(); - const Common::Vec3f rotation = GetRotations(); - const std::array<Common::Vec3f, 3> orientation = GetOrientation(); - const Common::Quaternion<f32> quaternion = GetQuaternion(); - return {accelerometer, gyroscope, rotation, orientation, quaternion}; -} - -Input::MotionStatus MotionInput::GetRandomMotion(int accel_magnitude, int gyro_magnitude) const { - std::random_device device; - std::mt19937 gen(device()); - std::uniform_int_distribution<s16> distribution(-1000, 1000); - const Common::Vec3f gyroscope{ - static_cast<f32>(distribution(gen)) * 0.001f, - static_cast<f32>(distribution(gen)) * 0.001f, - static_cast<f32>(distribution(gen)) * 0.001f, - }; - const Common::Vec3f accelerometer{ - static_cast<f32>(distribution(gen)) * 0.001f, - static_cast<f32>(distribution(gen)) * 0.001f, - static_cast<f32>(distribution(gen)) * 0.001f, - }; - constexpr Common::Vec3f rotation; - constexpr std::array orientation{ - Common::Vec3f{1.0f, 0.0f, 0.0f}, - Common::Vec3f{0.0f, 1.0f, 0.0f}, - Common::Vec3f{0.0f, 0.0f, 1.0f}, - }; - constexpr Common::Quaternion<f32> quaternion{ - {0.0f, 0.0f, 0.0f}, - 1.0f, - }; - return {accelerometer * accel_magnitude, gyroscope * gyro_magnitude, rotation, orientation, - quaternion}; -} - void MotionInput::ResetOrientation() { if (!reset_enabled || only_accelerometer) { return; @@ -304,4 +277,4 @@ void MotionInput::SetOrientationFromAccelerometer() { quat = quat.Normalized(); } } -} // namespace InputCommon +} // namespace Core::HID diff --git a/src/input_common/motion_input.h b/src/core/hid/motion_input.h index efe74cf19..5b5b420bb 100644 --- a/src/input_common/motion_input.h +++ b/src/core/hid/motion_input.h @@ -7,13 +7,12 @@ #include "common/common_types.h" #include "common/quaternion.h" #include "common/vector_math.h" -#include "core/frontend/input.h" -namespace InputCommon { +namespace Core::HID { class MotionInput { public: - explicit MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); + explicit MotionInput(); MotionInput(const MotionInput&) = default; MotionInput& operator=(const MotionInput&) = default; @@ -21,6 +20,7 @@ public: MotionInput(MotionInput&&) = default; MotionInput& operator=(MotionInput&&) = default; + void SetPID(f32 new_kp, f32 new_ki, f32 new_kd); void SetAcceleration(const Common::Vec3f& acceleration); void SetGyroscope(const Common::Vec3f& gyroscope); void SetQuaternion(const Common::Quaternion<f32>& quaternion); @@ -38,9 +38,6 @@ public: [[nodiscard]] Common::Vec3f GetGyroscope() const; [[nodiscard]] Common::Vec3f GetRotations() const; [[nodiscard]] Common::Quaternion<f32> GetQuaternion() const; - [[nodiscard]] Input::MotionStatus GetMotion() const; - [[nodiscard]] Input::MotionStatus GetRandomMotion(int accel_magnitude, - int gyro_magnitude) const; [[nodiscard]] bool IsMoving(f32 sensitivity) const; [[nodiscard]] bool IsCalibrated(f32 sensitivity) const; @@ -59,16 +56,32 @@ private: Common::Vec3f integral_error; Common::Vec3f derivative_error; + // Quaternion containing the device orientation Common::Quaternion<f32> quat{{0.0f, 0.0f, -1.0f}, 0.0f}; + + // Number of full rotations in each axis Common::Vec3f rotations; + + // Acceleration vector measurement in G force Common::Vec3f accel; + + // Gyroscope vector measurement in radians/s. Common::Vec3f gyro; + + // Vector to be substracted from gyro measurements Common::Vec3f gyro_drift; + // Minimum gyro amplitude to detect if the device is moving f32 gyro_threshold = 0.0f; + + // Number of invalid sequential data u32 reset_counter = 0; + + // If the provided data is invalid the device will be autocalibrated bool reset_enabled = true; + + // Use accelerometer values to calculate position bool only_accelerometer = true; }; -} // namespace InputCommon +} // namespace Core::HID diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index dd13d948f..d4fa69a77 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -1,36 +1,32 @@ add_library(input_common STATIC - analog_from_button.cpp - analog_from_button.h - keyboard.cpp - keyboard.h + drivers/gc_adapter.cpp + drivers/gc_adapter.h + drivers/keyboard.cpp + drivers/keyboard.h + drivers/mouse.cpp + drivers/mouse.h + drivers/sdl_driver.cpp + drivers/sdl_driver.h + drivers/tas_input.cpp + drivers/tas_input.h + drivers/touch_screen.cpp + drivers/touch_screen.h + drivers/udp_client.cpp + drivers/udp_client.h + helpers/stick_from_buttons.cpp + helpers/stick_from_buttons.h + helpers/touch_from_buttons.cpp + helpers/touch_from_buttons.h + helpers/udp_protocol.cpp + helpers/udp_protocol.h + input_engine.cpp + input_engine.h + input_mapping.cpp + input_mapping.h + input_poller.cpp + input_poller.h main.cpp main.h - motion_from_button.cpp - motion_from_button.h - motion_input.cpp - motion_input.h - touch_from_button.cpp - touch_from_button.h - gcadapter/gc_adapter.cpp - gcadapter/gc_adapter.h - gcadapter/gc_poller.cpp - gcadapter/gc_poller.h - mouse/mouse_input.cpp - mouse/mouse_input.h - mouse/mouse_poller.cpp - mouse/mouse_poller.h - sdl/sdl.cpp - sdl/sdl.h - tas/tas_input.cpp - tas/tas_input.h - tas/tas_poller.cpp - tas/tas_poller.h - udp/client.cpp - udp/client.h - udp/protocol.cpp - udp/protocol.h - udp/udp.cpp - udp/udp.h ) if (MSVC) @@ -57,8 +53,8 @@ endif() if (ENABLE_SDL2) target_sources(input_common PRIVATE - sdl/sdl_impl.cpp - sdl/sdl_impl.h + drivers/sdl_driver.cpp + drivers/sdl_driver.h ) target_link_libraries(input_common PRIVATE SDL2) target_compile_definitions(input_common PRIVATE HAVE_SDL2) diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp deleted file mode 100755 index 2fafd077f..000000000 --- a/src/input_common/analog_from_button.cpp +++ /dev/null @@ -1,238 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <atomic> -#include <chrono> -#include <cmath> -#include <thread> -#include "common/math_util.h" -#include "common/settings.h" -#include "input_common/analog_from_button.h" - -namespace InputCommon { - -class Analog final : public Input::AnalogDevice { -public: - using Button = std::unique_ptr<Input::ButtonDevice>; - - Analog(Button up_, Button down_, Button left_, Button right_, Button modifier_, - float modifier_scale_, float modifier_angle_) - : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), - right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), - modifier_angle(modifier_angle_) { - Input::InputCallback<bool> callbacks{ - [this]([[maybe_unused]] bool status) { UpdateStatus(); }}; - up->SetCallback(callbacks); - down->SetCallback(callbacks); - left->SetCallback(callbacks); - right->SetCallback(callbacks); - modifier->SetCallback(callbacks); - } - - bool IsAngleGreater(float old_angle, float new_angle) const { - constexpr float TAU = Common::PI * 2.0f; - // Use wider angle to ease the transition. - constexpr float aperture = TAU * 0.15f; - const float top_limit = new_angle + aperture; - return (old_angle > new_angle && old_angle <= top_limit) || - (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); - } - - bool IsAngleSmaller(float old_angle, float new_angle) const { - constexpr float TAU = Common::PI * 2.0f; - // Use wider angle to ease the transition. - constexpr float aperture = TAU * 0.15f; - const float bottom_limit = new_angle - aperture; - return (old_angle >= bottom_limit && old_angle < new_angle) || - (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); - } - - float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { - constexpr float TAU = Common::PI * 2.0f; - float new_angle = angle; - - auto time_difference = static_cast<float>( - std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); - time_difference /= 1000.0f * 1000.0f; - if (time_difference > 0.5f) { - time_difference = 0.5f; - } - - if (IsAngleGreater(new_angle, goal_angle)) { - new_angle -= modifier_angle * time_difference; - if (new_angle < 0) { - new_angle += TAU; - } - if (!IsAngleGreater(new_angle, goal_angle)) { - return goal_angle; - } - } else if (IsAngleSmaller(new_angle, goal_angle)) { - new_angle += modifier_angle * time_difference; - if (new_angle >= TAU) { - new_angle -= TAU; - } - if (!IsAngleSmaller(new_angle, goal_angle)) { - return goal_angle; - } - } else { - return goal_angle; - } - return new_angle; - } - - void SetGoalAngle(bool r, bool l, bool u, bool d) { - // Move to the right - if (r && !u && !d) { - goal_angle = 0.0f; - } - - // Move to the upper right - if (r && u && !d) { - goal_angle = Common::PI * 0.25f; - } - - // Move up - if (u && !l && !r) { - goal_angle = Common::PI * 0.5f; - } - - // Move to the upper left - if (l && u && !d) { - goal_angle = Common::PI * 0.75f; - } - - // Move to the left - if (l && !u && !d) { - goal_angle = Common::PI; - } - - // Move to the bottom left - if (l && !u && d) { - goal_angle = Common::PI * 1.25f; - } - - // Move down - if (d && !l && !r) { - goal_angle = Common::PI * 1.5f; - } - - // Move to the bottom right - if (r && !u && d) { - goal_angle = Common::PI * 1.75f; - } - } - - void UpdateStatus() { - const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; - - bool r = right->GetStatus(); - bool l = left->GetStatus(); - bool u = up->GetStatus(); - bool d = down->GetStatus(); - - // Eliminate contradictory movements - if (r && l) { - r = false; - l = false; - } - if (u && d) { - u = false; - d = false; - } - - // Move if a key is pressed - if (r || l || u || d) { - amplitude = coef; - } else { - amplitude = 0; - } - - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast<u64>( - std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); - - if (time_difference < 10) { - // Disable analog mode if inputs are too fast - SetGoalAngle(r, l, u, d); - angle = goal_angle; - } else { - angle = GetAngle(now); - SetGoalAngle(r, l, u, d); - } - - last_update = now; - } - - std::tuple<float, float> GetStatus() const override { - if (Settings::values.emulate_analog_keyboard) { - const auto now = std::chrono::steady_clock::now(); - float angle_ = GetAngle(now); - return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude); - } - constexpr float SQRT_HALF = 0.707106781f; - int x = 0, y = 0; - if (right->GetStatus()) { - ++x; - } - if (left->GetStatus()) { - --x; - } - if (up->GetStatus()) { - ++y; - } - if (down->GetStatus()) { - --y; - } - const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; - return std::make_tuple(static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF), - static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF)); - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {modifier_scale, 1.0f, 0.5f}; - } - - bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { - switch (direction) { - case Input::AnalogDirection::RIGHT: - return right->GetStatus(); - case Input::AnalogDirection::LEFT: - return left->GetStatus(); - case Input::AnalogDirection::UP: - return up->GetStatus(); - case Input::AnalogDirection::DOWN: - return down->GetStatus(); - } - return false; - } - -private: - Button up; - Button down; - Button left; - Button right; - Button modifier; - float modifier_scale; - float modifier_angle; - float angle{}; - float goal_angle{}; - float amplitude{}; - std::chrono::time_point<std::chrono::steady_clock> last_update; -}; - -std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { - const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); - auto up = Input::CreateDevice<Input::ButtonDevice>(params.Get("up", null_engine)); - auto down = Input::CreateDevice<Input::ButtonDevice>(params.Get("down", null_engine)); - auto left = Input::CreateDevice<Input::ButtonDevice>(params.Get("left", null_engine)); - auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine)); - auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine)); - auto modifier_scale = params.Get("modifier_scale", 0.5f); - auto modifier_angle = params.Get("modifier_angle", 5.5f); - return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left), - std::move(right), std::move(modifier), modifier_scale, - modifier_angle); -} - -} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index a2f1bb67c..7ab4540a8 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -2,47 +2,103 @@ // Licensed under GPLv2+ // Refer to the license.txt file included. -#include <chrono> -#include <thread> - +#include <fmt/format.h> #include <libusb.h> #include "common/logging/log.h" #include "common/param_package.h" #include "common/settings_input.h" -#include "input_common/gcadapter/gc_adapter.h" +#include "common/thread.h" +#include "input_common/drivers/gc_adapter.h" + +namespace InputCommon { + +class LibUSBContext { +public: + explicit LibUSBContext() { + init_result = libusb_init(&ctx); + } + + ~LibUSBContext() { + libusb_exit(ctx); + } + + LibUSBContext& operator=(const LibUSBContext&) = delete; + LibUSBContext(const LibUSBContext&) = delete; + + LibUSBContext& operator=(LibUSBContext&&) noexcept = delete; + LibUSBContext(LibUSBContext&&) noexcept = delete; + + [[nodiscard]] int InitResult() const noexcept { + return init_result; + } + + [[nodiscard]] libusb_context* get() noexcept { + return ctx; + } + +private: + libusb_context* ctx; + int init_result{}; +}; + +class LibUSBDeviceHandle { +public: + explicit LibUSBDeviceHandle(libusb_context* ctx, uint16_t vid, uint16_t pid) noexcept { + handle = libusb_open_device_with_vid_pid(ctx, vid, pid); + } + + ~LibUSBDeviceHandle() noexcept { + if (handle) { + libusb_release_interface(handle, 1); + libusb_close(handle); + } + } -namespace GCAdapter { + LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete; + LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete; -Adapter::Adapter() { - if (usb_adapter_handle != nullptr) { + LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete; + LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete; + + [[nodiscard]] libusb_device_handle* get() noexcept { + return handle; + } + +private: + libusb_device_handle* handle{}; +}; + +GCAdapter::GCAdapter(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + if (usb_adapter_handle) { return; } - LOG_INFO(Input, "GC Adapter Initialization started"); + LOG_DEBUG(Input, "Initialization started"); - const int init_res = libusb_init(&libusb_ctx); + libusb_ctx = std::make_unique<LibUSBContext>(); + const int init_res = libusb_ctx->InitResult(); if (init_res == LIBUSB_SUCCESS) { - adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + adapter_scan_thread = + std::jthread([this](std::stop_token stop_token) { AdapterScanThread(stop_token); }); } else { LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); } } -Adapter::~Adapter() { +GCAdapter::~GCAdapter() { Reset(); } -void Adapter::AdapterInputThread() { - LOG_DEBUG(Input, "GC Adapter input thread started"); +void GCAdapter::AdapterInputThread(std::stop_token stop_token) { + LOG_DEBUG(Input, "Input thread started"); + Common::SetCurrentThreadName("yuzu:input:GCAdapter"); s32 payload_size{}; AdapterPayload adapter_payload{}; - if (adapter_scan_thread.joinable()) { - adapter_scan_thread.join(); - } + adapter_scan_thread = {}; - while (adapter_input_thread_running) { - libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + while (!stop_token.stop_requested()) { + libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(), static_cast<s32>(adapter_payload.size()), &payload_size, 16); if (IsPayloadCorrect(adapter_payload, payload_size)) { UpdateControllers(adapter_payload); @@ -52,19 +108,20 @@ void Adapter::AdapterInputThread() { } if (restart_scan_thread) { - adapter_scan_thread = std::thread(&Adapter::AdapterScanThread, this); + adapter_scan_thread = + std::jthread([this](std::stop_token token) { AdapterScanThread(token); }); restart_scan_thread = false; } } -bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { +bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { if (payload_size != static_cast<s32>(adapter_payload.size()) || adapter_payload[0] != LIBUSB_DT_HID) { LOG_DEBUG(Input, "Error reading payload (size: {}, type: {:02x})", payload_size, adapter_payload[0]); if (input_error_counter++ > 20) { - LOG_ERROR(Input, "GC adapter timeout, Is the adapter connected?"); - adapter_input_thread_running = false; + LOG_ERROR(Input, "Timeout, Is the adapter connected?"); + adapter_input_thread.request_stop(); restart_scan_thread = true; } return false; @@ -74,7 +131,7 @@ bool Adapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payloa return true; } -void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { +void GCAdapter::UpdateControllers(const AdapterPayload& adapter_payload) { for (std::size_t port = 0; port < pads.size(); ++port) { const std::size_t offset = 1 + (9 * port); const auto type = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); @@ -84,23 +141,24 @@ void Adapter::UpdateControllers(const AdapterPayload& adapter_payload) { const u8 b2 = adapter_payload[offset + 2]; UpdateStateButtons(port, b1, b2); UpdateStateAxes(port, adapter_payload); - if (configuring) { - UpdateYuzuSettings(port); - } } } } -void Adapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { +void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { if (pads[port].type == pad_type) { return; } // Device changed reset device and set new type - ResetDevice(port); + pads[port].axis_origin = {}; + pads[port].reset_origin_counter = {}; + pads[port].enable_vibration = {}; + pads[port].rumble_amplitude = {}; pads[port].type = pad_type; } -void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { +void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1, + [[maybe_unused]] u8 b2) { if (port >= pads.size()) { return; } @@ -116,25 +174,21 @@ void Adapter::UpdateStateButtons(std::size_t port, u8 b1, u8 b2) { PadButton::TriggerR, PadButton::TriggerL, }; - pads[port].buttons = 0; + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { - if ((b1 & (1U << i)) != 0) { - pads[port].buttons = - static_cast<u16>(pads[port].buttons | static_cast<u16>(b1_buttons[i])); - pads[port].last_button = b1_buttons[i]; - } + const bool button_status = (b1 & (1U << i)) != 0; + const int button = static_cast<int>(b1_buttons[i]); + SetButton(pads[port].identifier, button, button_status); } for (std::size_t j = 0; j < b2_buttons.size(); ++j) { - if ((b2 & (1U << j)) != 0) { - pads[port].buttons = - static_cast<u16>(pads[port].buttons | static_cast<u16>(b2_buttons[j])); - pads[port].last_button = b2_buttons[j]; - } + const bool button_status = (b2 & (1U << j)) != 0; + const int button = static_cast<int>(b2_buttons[j]); + SetButton(pads[port].identifier, button, button_status); } } -void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { +void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { if (port >= pads.size()) { return; } @@ -155,134 +209,63 @@ void Adapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_pa pads[port].axis_origin[index] = axis_value; pads[port].reset_origin_counter++; } - pads[port].axis_values[index] = - static_cast<s16>(axis_value - pads[port].axis_origin[index]); - } -} - -void Adapter::UpdateYuzuSettings(std::size_t port) { - if (port >= pads.size()) { - return; - } - - constexpr u8 axis_threshold = 50; - GCPadStatus pad_status = {.port = port}; - - if (pads[port].buttons != 0) { - pad_status.button = pads[port].last_button; - pad_queue.Push(pad_status); - } - - // Accounting for a threshold here to ensure an intentional press - for (std::size_t i = 0; i < pads[port].axis_values.size(); ++i) { - const s16 value = pads[port].axis_values[i]; - - if (value > axis_threshold || value < -axis_threshold) { - pad_status.axis = static_cast<PadAxes>(i); - pad_status.axis_value = value; - pad_status.axis_threshold = axis_threshold; - pad_queue.Push(pad_status); - } - } -} - -void Adapter::UpdateVibrations() { - // Use 8 states to keep the switching between on/off fast enough for - // a human to not notice the difference between switching from on/off - // More states = more rumble strengths = slower update time - constexpr u8 vibration_states = 8; - - vibration_counter = (vibration_counter + 1) % vibration_states; - - for (GCController& pad : pads) { - const bool vibrate = pad.rumble_amplitude > vibration_counter; - vibration_changed |= vibrate != pad.enable_vibration; - pad.enable_vibration = vibrate; - } - SendVibrations(); -} - -void Adapter::SendVibrations() { - if (!rumble_enabled || !vibration_changed) { - return; - } - s32 size{}; - constexpr u8 rumble_command = 0x11; - const u8 p1 = pads[0].enable_vibration; - const u8 p2 = pads[1].enable_vibration; - const u8 p3 = pads[2].enable_vibration; - const u8 p4 = pads[3].enable_vibration; - std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4}; - const int err = libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, payload.data(), - static_cast<s32>(payload.size()), &size, 16); - if (err) { - LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err)); - if (output_error_counter++ > 5) { - LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled"); - rumble_enabled = false; - } - return; + const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f; + SetAxis(pads[port].identifier, static_cast<int>(index), axis_status); } - output_error_counter = 0; - vibration_changed = false; } -bool Adapter::RumblePlay(std::size_t port, u8 amplitude) { - pads[port].rumble_amplitude = amplitude; - - return rumble_enabled; -} - -void Adapter::AdapterScanThread() { - adapter_scan_thread_running = true; - adapter_input_thread_running = false; - if (adapter_input_thread.joinable()) { - adapter_input_thread.join(); - } - ClearLibusbHandle(); - ResetDevices(); - while (adapter_scan_thread_running && !adapter_input_thread_running) { - Setup(); - std::this_thread::sleep_for(std::chrono::seconds(1)); +void GCAdapter::AdapterScanThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("yuzu:input:ScanGCAdapter"); + usb_adapter_handle = nullptr; + pads = {}; + while (!stop_token.stop_requested() && !Setup()) { + std::this_thread::sleep_for(std::chrono::seconds(2)); } } -void Adapter::Setup() { - usb_adapter_handle = libusb_open_device_with_vid_pid(libusb_ctx, 0x057e, 0x0337); - - if (usb_adapter_handle == NULL) { - return; +bool GCAdapter::Setup() { + constexpr u16 nintendo_vid = 0x057e; + constexpr u16 gc_adapter_pid = 0x0337; + usb_adapter_handle = + std::make_unique<LibUSBDeviceHandle>(libusb_ctx->get(), nintendo_vid, gc_adapter_pid); + if (!usb_adapter_handle->get()) { + return false; } if (!CheckDeviceAccess()) { - ClearLibusbHandle(); - return; + usb_adapter_handle = nullptr; + return false; } - libusb_device* device = libusb_get_device(usb_adapter_handle); + libusb_device* const device = libusb_get_device(usb_adapter_handle->get()); LOG_INFO(Input, "GC adapter is now connected"); // GC Adapter found and accessible, registering it if (GetGCEndpoint(device)) { - adapter_scan_thread_running = false; - adapter_input_thread_running = true; rumble_enabled = true; input_error_counter = 0; output_error_counter = 0; - adapter_input_thread = std::thread(&Adapter::AdapterInputThread, this); - } -} -bool Adapter::CheckDeviceAccess() { - // This fixes payload problems from offbrand GCAdapters - const s32 control_transfer_error = - libusb_control_transfer(usb_adapter_handle, 0x21, 11, 0x0001, 0, nullptr, 0, 1000); - if (control_transfer_error < 0) { - LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); + std::size_t port = 0; + for (GCController& pad : pads) { + pad.identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = port++, + .pad = 0, + }; + PreSetController(pad.identifier); + } + + adapter_input_thread = + std::jthread([this](std::stop_token stop_token) { AdapterInputThread(stop_token); }); + return true; } + return false; +} - s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); +bool GCAdapter::CheckDeviceAccess() { + s32 kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle->get(), 0); if (kernel_driver_error == 1) { - kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 0); + kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle->get(), 0); if (kernel_driver_error != 0 && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { LOG_ERROR(Input, "libusb_detach_kernel_driver failed with error = {}", kernel_driver_error); @@ -290,23 +273,28 @@ bool Adapter::CheckDeviceAccess() { } if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { - libusb_close(usb_adapter_handle); usb_adapter_handle = nullptr; return false; } - const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); + const int interface_claim_error = libusb_claim_interface(usb_adapter_handle->get(), 0); if (interface_claim_error) { LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); - libusb_close(usb_adapter_handle); usb_adapter_handle = nullptr; return false; } + // This fixes payload problems from offbrand GCAdapters + const s32 control_transfer_error = + libusb_control_transfer(usb_adapter_handle->get(), 0x21, 11, 0x0001, 0, nullptr, 0, 1000); + if (control_transfer_error < 0) { + LOG_ERROR(Input, "libusb_control_transfer failed with error= {}", control_transfer_error); + } + return true; } -bool Adapter::GetGCEndpoint(libusb_device* device) { +bool GCAdapter::GetGCEndpoint(libusb_device* device) { libusb_config_descriptor* config = nullptr; const int config_descriptor_return = libusb_get_config_descriptor(device, 0, &config); if (config_descriptor_return != LIBUSB_SUCCESS) { @@ -332,77 +320,95 @@ bool Adapter::GetGCEndpoint(libusb_device* device) { // This transfer seems to be responsible for clearing the state of the adapter // Used to clear the "busy" state of when the device is unexpectedly unplugged unsigned char clear_payload = 0x13; - libusb_interrupt_transfer(usb_adapter_handle, output_endpoint, &clear_payload, + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, &clear_payload, sizeof(clear_payload), nullptr, 16); return true; } -void Adapter::JoinThreads() { - restart_scan_thread = false; - adapter_input_thread_running = false; - adapter_scan_thread_running = false; +Common::Input::VibrationError GCAdapter::SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { + const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; + const auto processed_amplitude = + static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); - if (adapter_scan_thread.joinable()) { - adapter_scan_thread.join(); - } + pads[identifier.port].rumble_amplitude = processed_amplitude; - if (adapter_input_thread.joinable()) { - adapter_input_thread.join(); + if (!rumble_enabled) { + return Common::Input::VibrationError::Disabled; } + return Common::Input::VibrationError::None; } -void Adapter::ClearLibusbHandle() { - if (usb_adapter_handle) { - libusb_release_interface(usb_adapter_handle, 1); - libusb_close(usb_adapter_handle); - usb_adapter_handle = nullptr; +void GCAdapter::UpdateVibrations() { + // Use 8 states to keep the switching between on/off fast enough for + // a human to feel different vibration strenght + // More states == more rumble strengths == slower update time + constexpr u8 vibration_states = 8; + + vibration_counter = (vibration_counter + 1) % vibration_states; + + for (GCController& pad : pads) { + const bool vibrate = pad.rumble_amplitude > vibration_counter; + vibration_changed |= vibrate != pad.enable_vibration; + pad.enable_vibration = vibrate; } + SendVibrations(); } -void Adapter::ResetDevices() { - for (std::size_t i = 0; i < pads.size(); ++i) { - ResetDevice(i); +void GCAdapter::SendVibrations() { + if (!rumble_enabled || !vibration_changed) { + return; + } + s32 size{}; + constexpr u8 rumble_command = 0x11; + const u8 p1 = pads[0].enable_vibration; + const u8 p2 = pads[1].enable_vibration; + const u8 p3 = pads[2].enable_vibration; + const u8 p4 = pads[3].enable_vibration; + std::array<u8, 5> payload = {rumble_command, p1, p2, p3, p4}; + const int err = + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(), + static_cast<s32>(payload.size()), &size, 16); + if (err) { + LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err)); + if (output_error_counter++ > 5) { + LOG_ERROR(Input, "Output timeout, Rumble disabled"); + rumble_enabled = false; + } + return; } + output_error_counter = 0; + vibration_changed = false; } -void Adapter::ResetDevice(std::size_t port) { - pads[port].type = ControllerTypes::None; - pads[port].enable_vibration = false; - pads[port].rumble_amplitude = 0; - pads[port].buttons = 0; - pads[port].last_button = PadButton::Undefined; - pads[port].axis_values.fill(0); - pads[port].reset_origin_counter = 0; +bool GCAdapter::DeviceConnected(std::size_t port) const { + return pads[port].type != ControllerTypes::None; } -void Adapter::Reset() { - JoinThreads(); - ClearLibusbHandle(); - ResetDevices(); - - if (libusb_ctx) { - libusb_exit(libusb_ctx); - } +void GCAdapter::Reset() { + adapter_scan_thread = {}; + adapter_input_thread = {}; + usb_adapter_handle = nullptr; + pads = {}; + libusb_ctx = nullptr; } -std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { +std::vector<Common::ParamPackage> GCAdapter::GetInputDevices() const { std::vector<Common::ParamPackage> devices; for (std::size_t port = 0; port < pads.size(); ++port) { if (!DeviceConnected(port)) { continue; } - std::string name = fmt::format("Gamecube Controller {}", port + 1); - devices.emplace_back(Common::ParamPackage{ - {"class", "gcpad"}, - {"display", std::move(name)}, - {"port", std::to_string(port)}, - }); + Common::ParamPackage identifier{}; + identifier.Set("engine", GetEngineName()); + identifier.Set("display", fmt::format("Gamecube Controller {}", port + 1)); + identifier.Set("port", static_cast<int>(port)); + devices.emplace_back(identifier); } return devices; } -InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( - const Common::ParamPackage& params) const { +ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& params) { // This list is missing ZL/ZR since those are not considered buttons. // We will add those afterwards // This list also excludes any button that can't be really mapped @@ -425,47 +431,49 @@ InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( return {}; } - InputCommon::ButtonMapping mapping{}; + ButtonMapping mapping{}; for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { - Common::ParamPackage button_params({{"engine", "gcpad"}}); + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); button_params.Set("port", params.Get("port", 0)); button_params.Set("button", static_cast<int>(gcadapter_button)); mapping.insert_or_assign(switch_button, std::move(button_params)); } // Add the missing bindings for ZL/ZR - static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2> + static constexpr std::array<std::tuple<Settings::NativeButton::Values, PadButton, PadAxes>, 2> switch_to_gcadapter_axis = { - std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, - {Settings::NativeButton::ZR, PadAxes::TriggerRight}, + std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft}, + {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight}, }; - for (const auto& [switch_button, gcadapter_axis] : switch_to_gcadapter_axis) { - Common::ParamPackage button_params({{"engine", "gcpad"}}); + for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) { + Common::ParamPackage button_params{}; + button_params.Set("engine", GetEngineName()); button_params.Set("port", params.Get("port", 0)); - button_params.Set("button", static_cast<s32>(PadButton::Stick)); + button_params.Set("button", static_cast<s32>(gcadapter_buton)); button_params.Set("axis", static_cast<s32>(gcadapter_axis)); button_params.Set("threshold", 0.5f); + button_params.Set("range", 1.9f); button_params.Set("direction", "+"); mapping.insert_or_assign(switch_button, std::move(button_params)); } return mapping; } -InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( - const Common::ParamPackage& params) const { +AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) { if (!params.Has("port")) { return {}; } - InputCommon::AnalogMapping mapping = {}; + AnalogMapping mapping = {}; Common::ParamPackage left_analog_params; - left_analog_params.Set("engine", "gcpad"); + left_analog_params.Set("engine", GetEngineName()); left_analog_params.Set("port", params.Get("port", 0)); left_analog_params.Set("axis_x", static_cast<int>(PadAxes::StickX)); left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); Common::ParamPackage right_analog_params; - right_analog_params.Set("engine", "gcpad"); + right_analog_params.Set("engine", GetEngineName()); right_analog_params.Set("port", params.Get("port", 0)); right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); @@ -473,34 +481,47 @@ InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( return mapping; } -bool Adapter::DeviceConnected(std::size_t port) const { - return pads[port].type != ControllerTypes::None; -} - -void Adapter::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; -} - -void Adapter::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue<GCPadStatus>& Adapter::GetPadQueue() const { - return pad_queue; +Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { + PadButton button = static_cast<PadButton>(params.Get("button", 0)); + switch (button) { + case PadButton::ButtonLeft: + return Common::Input::ButtonNames::ButtonLeft; + case PadButton::ButtonRight: + return Common::Input::ButtonNames::ButtonRight; + case PadButton::ButtonDown: + return Common::Input::ButtonNames::ButtonDown; + case PadButton::ButtonUp: + return Common::Input::ButtonNames::ButtonUp; + case PadButton::TriggerZ: + return Common::Input::ButtonNames::TriggerZ; + case PadButton::TriggerR: + return Common::Input::ButtonNames::TriggerR; + case PadButton::TriggerL: + return Common::Input::ButtonNames::TriggerL; + case PadButton::ButtonA: + return Common::Input::ButtonNames::ButtonA; + case PadButton::ButtonB: + return Common::Input::ButtonNames::ButtonB; + case PadButton::ButtonX: + return Common::Input::ButtonNames::ButtonX; + case PadButton::ButtonY: + return Common::Input::ButtonNames::ButtonY; + case PadButton::ButtonStart: + return Common::Input::ButtonNames::ButtonStart; + default: + return Common::Input::ButtonNames::Undefined; + } } -GCController& Adapter::GetPadState(std::size_t port) { - return pads.at(port); -} +Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return GetUIButtonName(params); + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } -const GCController& Adapter::GetPadState(std::size_t port) const { - return pads.at(port); + return Common::Input::ButtonNames::Invalid; } -} // namespace GCAdapter +} // namespace InputCommon diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h new file mode 100644 index 000000000..7ce1912a3 --- /dev/null +++ b/src/input_common/drivers/gc_adapter.h @@ -0,0 +1,135 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <memory> +#include <mutex> +#include <stop_token> +#include <string> +#include <thread> + +#include "input_common/input_engine.h" + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +namespace InputCommon { + +class LibUSBContext; +class LibUSBDeviceHandle; + +class GCAdapter : public InputEngine { +public: + explicit GCAdapter(std::string input_engine_); + ~GCAdapter() override; + + Common::Input::VibrationError SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; + + /// 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; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: + enum class PadButton { + Undefined = 0x0000, + ButtonLeft = 0x0001, + ButtonRight = 0x0002, + ButtonDown = 0x0004, + ButtonUp = 0x0008, + TriggerZ = 0x0010, + TriggerR = 0x0020, + TriggerL = 0x0040, + ButtonA = 0x0100, + ButtonB = 0x0200, + ButtonX = 0x0400, + ButtonY = 0x0800, + ButtonStart = 0x1000, + }; + + enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, + }; + + enum class ControllerTypes { + None, + Wired, + Wireless, + }; + + struct GCController { + ControllerTypes type = ControllerTypes::None; + PadIdentifier identifier{}; + bool enable_vibration = false; + u8 rumble_amplitude{}; + std::array<u8, 6> axis_origin{}; + u8 reset_origin_counter{}; + }; + + using AdapterPayload = std::array<u8, 37>; + + void UpdatePadType(std::size_t port, ControllerTypes pad_type); + void UpdateControllers(const AdapterPayload& adapter_payload); + void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); + void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); + + void AdapterInputThread(std::stop_token stop_token); + + void AdapterScanThread(std::stop_token stop_token); + + bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); + + /// For use in initialization, querying devices to find the adapter + bool Setup(); + + /// Returns true if we successfully gain access to GC Adapter + bool CheckDeviceAccess(); + + /// Captures GC Adapter endpoint address + /// Returns true if the endpoint was set correctly + bool GetGCEndpoint(libusb_device* device); + + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port) const; + + /// For shutting down, clear all data, join all threads, release usb + void Reset(); + + void UpdateVibrations(); + + /// Updates vibration state of all controllers + void SendVibrations(); + + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + + std::unique_ptr<LibUSBDeviceHandle> usb_adapter_handle; + std::array<GCController, 4> pads; + + std::jthread adapter_input_thread; + std::jthread adapter_scan_thread; + bool restart_scan_thread{}; + + std::unique_ptr<LibUSBContext> libusb_ctx; + + u8 input_endpoint{0}; + u8 output_endpoint{0}; + u8 input_error_counter{0}; + u8 output_error_counter{0}; + int vibration_counter{0}; + + bool rumble_enabled{true}; + bool vibration_changed{true}; +}; +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp new file mode 100644 index 000000000..4c1e5bbec --- /dev/null +++ b/src/input_common/drivers/keyboard.cpp @@ -0,0 +1,112 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/param_package.h" +#include "common/settings_input.h" +#include "input_common/drivers/keyboard.h" + +namespace InputCommon { + +constexpr PadIdentifier key_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; +constexpr PadIdentifier keyboard_key_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 1, + .pad = 0, +}; +constexpr PadIdentifier keyboard_modifier_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 1, + .pad = 1, +}; + +Keyboard::Keyboard(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + // Keyboard is broken into 3 diferent sets: + // key: Unfiltered intended for controllers. + // keyboard_key: Allows only Settings::NativeKeyboard::Keys intended for keyboard emulation. + // keyboard_modifier: Allows only Settings::NativeKeyboard::Modifiers intended for keyboard + // emulation. + PreSetController(key_identifier); + PreSetController(keyboard_key_identifier); + PreSetController(keyboard_modifier_identifier); +} + +void Keyboard::PressKey(int key_code) { + SetButton(key_identifier, key_code, true); +} + +void Keyboard::ReleaseKey(int key_code) { + SetButton(key_identifier, key_code, false); +} + +void Keyboard::PressKeyboardKey(int key_index) { + if (key_index == Settings::NativeKeyboard::None) { + return; + } + SetButton(keyboard_key_identifier, key_index, true); +} + +void Keyboard::ReleaseKeyboardKey(int key_index) { + if (key_index == Settings::NativeKeyboard::None) { + return; + } + SetButton(keyboard_key_identifier, key_index, false); +} + +void Keyboard::SetKeyboardModifiers(int key_modifiers) { + for (int i = 0; i < 32; ++i) { + bool key_value = ((key_modifiers >> i) & 0x1) != 0; + SetButton(keyboard_modifier_identifier, i, key_value); + // Use the modifier to press the key button equivalent + switch (i) { + case Settings::NativeKeyboard::LeftControl: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value); + break; + case Settings::NativeKeyboard::LeftShift: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value); + break; + case Settings::NativeKeyboard::LeftAlt: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value); + break; + case Settings::NativeKeyboard::LeftMeta: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value); + break; + case Settings::NativeKeyboard::RightControl: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey, + key_value); + break; + case Settings::NativeKeyboard::RightShift: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value); + break; + case Settings::NativeKeyboard::RightAlt: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value); + break; + case Settings::NativeKeyboard::RightMeta: + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value); + break; + default: + // Other modifier keys should be pressed with PressKey since they stay enabled until + // next press + break; + } + } +} + +void Keyboard::ReleaseAllKeys() { + ResetButtonState(); +} + +std::vector<Common::ParamPackage> Keyboard::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", "Keyboard Only"}, + }); + return devices; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h new file mode 100644 index 000000000..3856c882c --- /dev/null +++ b/src/input_common/drivers/keyboard.h @@ -0,0 +1,56 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Keyboard final : public InputEngine { +public: + explicit Keyboard(std::string input_engine_); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressKey(int key_code); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseKey(int key_code); + + /** + * Sets the status of the keyboard key to pressed + * @param key_index index of the key to press + */ + void PressKeyboardKey(int key_index); + + /** + * Sets the status of the keyboard key to released + * @param key_index index of the key to release + */ + void ReleaseKeyboardKey(int key_index); + + /** + * Sets the status of all keyboard modifier keys + * @param key_modifiers the code of the key to release + */ + void SetKeyboardModifiers(int key_modifiers); + + /// Sets all keys to the non pressed state + void ReleaseAllKeys(); + + /// Used for automapping features + std::vector<Common::ParamPackage> GetInputDevices() const override; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp new file mode 100644 index 000000000..aa69216c8 --- /dev/null +++ b/src/input_common/drivers/mouse.cpp @@ -0,0 +1,185 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include <stop_token> +#include <thread> +#include <fmt/format.h> + +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/mouse.h" + +namespace InputCommon { +constexpr int mouse_axis_x = 0; +constexpr int mouse_axis_y = 1; +constexpr int wheel_axis_x = 2; +constexpr int wheel_axis_y = 3; +constexpr int touch_axis_x = 10; +constexpr int touch_axis_y = 11; +constexpr PadIdentifier identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; + +Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); + PreSetAxis(identifier, mouse_axis_x); + PreSetAxis(identifier, mouse_axis_y); + PreSetAxis(identifier, wheel_axis_x); + PreSetAxis(identifier, wheel_axis_y); + PreSetAxis(identifier, touch_axis_x); + PreSetAxis(identifier, touch_axis_x); + update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); +} + +void Mouse::UpdateThread(std::stop_token stop_token) { + Common::SetCurrentThreadName("yuzu:input:Mouse"); + constexpr int update_time = 10; + while (!stop_token.stop_requested()) { + if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { + // Slow movement by 4% + last_mouse_change *= 0.96f; + const float sensitivity = + Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f; + SetAxis(identifier, mouse_axis_x, last_mouse_change.x * sensitivity); + SetAxis(identifier, mouse_axis_y, -last_mouse_change.y * sensitivity); + } + + if (mouse_panning_timout++ > 20) { + StopPanning(); + } + std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); + } +} + +void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y) { + // If native mouse is enabled just set the screen coordinates + if (Settings::values.mouse_enabled) { + SetAxis(identifier, mouse_axis_x, touch_x); + SetAxis(identifier, mouse_axis_y, touch_y); + return; + } + + SetAxis(identifier, touch_axis_x, touch_x); + SetAxis(identifier, touch_axis_y, touch_y); + + if (Settings::values.mouse_panning) { + auto mouse_change = + (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); + mouse_panning_timout = 0; + + const auto move_distance = mouse_change.Length(); + if (move_distance == 0) { + return; + } + + // Make slow movements at least 3 units on lenght + if (move_distance < 3.0f) { + // Normalize value + mouse_change /= move_distance; + mouse_change *= 3.0f; + } + + // Average mouse movements + last_mouse_change = (last_mouse_change * 0.91f) + (mouse_change * 0.09f); + + const auto last_move_distance = last_mouse_change.Length(); + + // Make fast movements clamp to 8 units on lenght + if (last_move_distance > 8.0f) { + // Normalize value + last_mouse_change /= last_move_distance; + last_mouse_change *= 8.0f; + } + + // Ignore average if it's less than 1 unit and use current movement value + if (last_move_distance < 1.0f) { + last_mouse_change = mouse_change / mouse_change.Length(); + } + + return; + } + + if (button_pressed) { + const auto mouse_move = Common::MakeVec<int>(x, y) - mouse_origin; + const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; + SetAxis(identifier, mouse_axis_x, static_cast<float>(mouse_move.x) * sensitivity); + SetAxis(identifier, mouse_axis_y, static_cast<float>(-mouse_move.y) * sensitivity); + } +} + +void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button) { + SetAxis(identifier, touch_axis_x, touch_x); + SetAxis(identifier, touch_axis_y, touch_y); + SetButton(identifier, static_cast<int>(button), true); + // Set initial analog parameters + mouse_origin = {x, y}; + last_mouse_position = {x, y}; + button_pressed = true; +} + +void Mouse::ReleaseButton(MouseButton button) { + SetButton(identifier, static_cast<int>(button), false); + + if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) { + SetAxis(identifier, mouse_axis_x, 0); + SetAxis(identifier, mouse_axis_y, 0); + } + button_pressed = false; +} + +void Mouse::MouseWheelChange(int x, int y) { + wheel_position.x += x; + wheel_position.y += y; + SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position.x)); + SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position.y)); +} + +void Mouse::ReleaseAllButtons() { + ResetButtonState(); + button_pressed = false; +} + +void Mouse::StopPanning() { + last_mouse_change = {}; +} + +std::vector<Common::ParamPackage> Mouse::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", "Keyboard/Mouse"}, + }); + return devices; +} + +AnalogMapping Mouse::GetAnalogMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + // Only overwrite different buttons from default + AnalogMapping mapping = {}; + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", GetEngineName()); + right_analog_params.Set("axis_x", 0); + right_analog_params.Set("axis_y", 1); + right_analog_params.Set("threshold", 0.5f); + right_analog_params.Set("range", 1.0f); + right_analog_params.Set("deadzone", 0.0f); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + + return Common::Input::ButtonNames::Invalid; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h new file mode 100644 index 000000000..040446178 --- /dev/null +++ b/src/input_common/drivers/mouse.h @@ -0,0 +1,81 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include <stop_token> +#include <thread> + +#include "common/vector_math.h" +#include "input_common/input_engine.h" + +namespace InputCommon { + +enum class MouseButton { + Left, + Right, + Wheel, + Backward, + Forward, + Task, + Extra, + Undefined, +}; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class Mouse final : public InputEngine { +public: + explicit Mouse(std::string input_engine_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int center_y); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton button); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void ReleaseButton(MouseButton button); + + /** + * Sets the status of the mouse wheel + * @param x delta movement in the x direction + * @param y delta movement in the y direction + */ + void MouseWheelChange(int x, int y); + + void ReleaseAllButtons(); + + std::vector<Common::ParamPackage> GetInputDevices() const override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; + +private: + void UpdateThread(std::stop_token stop_token); + void StopPanning(); + + Common::Vec2<int> mouse_origin; + Common::Vec2<int> last_mouse_position; + Common::Vec2<float> last_mouse_change; + Common::Vec2<int> wheel_position; + bool button_pressed; + int mouse_panning_timout{}; + std::jthread update_thread; +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp new file mode 100644 index 000000000..0cda9df62 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.cpp @@ -0,0 +1,926 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "common/logging/log.h" +#include "common/math_util.h" +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "common/vector_math.h" +#include "input_common/drivers/sdl_driver.h" + +namespace InputCommon { + +namespace { +std::string GetGUID(SDL_Joystick* joystick) { + const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); + char guid_str[33]; + SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); + return guid_str; +} +} // Anonymous namespace + +static int SDLEventWatcher(void* user_data, SDL_Event* event) { + auto* const sdl_state = static_cast<SDLDriver*>(user_data); + + sdl_state->HandleGameControllerEvent(*event); + + return 0; +} + +class SDLJoystick { +public: + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + SDL_GameController* game_controller) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, + sdl_controller{game_controller, &SDL_GameControllerClose} { + EnableMotion(); + } + + void EnableMotion() { + if (sdl_controller) { + SDL_GameController* controller = sdl_controller.get(); + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); + has_accel = true; + } + if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { + SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); + has_gyro = true; + } + } + } + + bool HasGyro() const { + return has_gyro; + } + + bool HasAccel() const { + return has_accel; + } + + bool UpdateMotion(SDL_ControllerSensorEvent event) { + constexpr float gravity_constant = 9.80665f; + std::lock_guard lock{mutex}; + const u64 time_difference = event.timestamp - last_motion_update; + last_motion_update = event.timestamp; + switch (event.sensor) { + case SDL_SENSOR_ACCEL: { + motion.accel_x = -event.data[0] / gravity_constant; + motion.accel_y = event.data[2] / gravity_constant; + motion.accel_z = -event.data[1] / gravity_constant; + break; + } + case SDL_SENSOR_GYRO: { + motion.gyro_x = event.data[0] / (Common::PI * 2); + motion.gyro_y = -event.data[2] / (Common::PI * 2); + motion.gyro_z = event.data[1] / (Common::PI * 2); + break; + } + } + + // Ignore duplicated timestamps + if (time_difference == 0) { + return false; + } + motion.delta_timestamp = time_difference * 1000; + return true; + } + + const BasicMotion& GetMotion() const { + return motion; + } + + bool RumblePlay(const Common::Input::VibrationStatus vibration) { + constexpr u32 rumble_max_duration_ms = 1000; + if (sdl_controller) { + return SDL_GameControllerRumble( + sdl_controller.get(), static_cast<u16>(vibration.low_amplitude), + static_cast<u16>(vibration.high_amplitude), rumble_max_duration_ms) != -1; + } else if (sdl_joystick) { + return SDL_JoystickRumble(sdl_joystick.get(), static_cast<u16>(vibration.low_amplitude), + static_cast<u16>(vibration.high_amplitude), + rumble_max_duration_ms) != -1; + } + + return false; + } + + bool HasHDRumble() const { + if (sdl_controller) { + return (SDL_GameControllerGetType(sdl_controller.get()) == + SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO); + } + return false; + } + /** + * The Pad identifier of the joystick + */ + const PadIdentifier GetPadIdentifier() const { + return { + .guid = Common::UUID{guid}, + .port = static_cast<std::size_t>(port), + .pad = 0, + }; + } + + /** + * The guid of the joystick + */ + const std::string& GetGUID() const { + return guid; + } + + /** + * The number of joystick from the same type that were connected before this joystick + */ + int GetPort() const { + return port; + } + + SDL_Joystick* GetSDLJoystick() const { + return sdl_joystick.get(); + } + + SDL_GameController* GetSDLGameController() const { + return sdl_controller.get(); + } + + void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + sdl_joystick.reset(joystick); + sdl_controller.reset(controller); + } + + bool IsJoyconLeft() const { + const std::string controller_name = GetControllerName(); + if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) { + return true; + } + if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) { + return true; + } + return false; + } + + bool IsJoyconRight() const { + const std::string controller_name = GetControllerName(); + if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) { + return true; + } + if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) { + return true; + } + return false; + } + + BatteryLevel GetBatteryLevel() { + const auto level = SDL_JoystickCurrentPowerLevel(sdl_joystick.get()); + switch (level) { + case SDL_JOYSTICK_POWER_EMPTY: + return BatteryLevel::Empty; + case SDL_JOYSTICK_POWER_LOW: + return BatteryLevel::Critical; + case SDL_JOYSTICK_POWER_MEDIUM: + return BatteryLevel::Low; + case SDL_JOYSTICK_POWER_FULL: + return BatteryLevel::Medium; + case SDL_JOYSTICK_POWER_MAX: + return BatteryLevel::Full; + case SDL_JOYSTICK_POWER_UNKNOWN: + case SDL_JOYSTICK_POWER_WIRED: + default: + return BatteryLevel::Charging; + } + } + + std::string GetControllerName() const { + if (sdl_controller) { + switch (SDL_GameControllerGetType(sdl_controller.get())) { + case SDL_CONTROLLER_TYPE_XBOX360: + return "XBox 360 Controller"; + case SDL_CONTROLLER_TYPE_XBOXONE: + return "XBox One Controller"; + default: + break; + } + const auto name = SDL_GameControllerName(sdl_controller.get()); + if (name) { + return name; + } + } + + if (sdl_joystick) { + const auto name = SDL_JoystickName(sdl_joystick.get()); + if (name) { + return name; + } + } + + return "Unknown"; + } + +private: + std::string guid; + int port; + std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; + std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; + mutable std::mutex mutex; + + u64 last_motion_update{}; + bool has_gyro{false}; + bool has_accel{false}; + BasicMotion motion; +}; + +std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickByGUID(const std::string& guid, int port) { + std::lock_guard lock{joystick_map_mutex}; + const auto it = joystick_map.find(guid); + + if (it != joystick_map.end()) { + while (it->second.size() <= static_cast<std::size_t>(port)) { + auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), + nullptr, nullptr); + it->second.emplace_back(std::move(joystick)); + } + + return it->second[static_cast<std::size_t>(port)]; + } + + auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr); + + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +std::shared_ptr<SDLJoystick> SDLDriver::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { + auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + const std::string guid = GetGUID(sdl_joystick); + + std::lock_guard lock{joystick_map_mutex}; + const auto map_it = joystick_map.find(guid); + + if (map_it == joystick_map.end()) { + return nullptr; + } + + const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (vec_it == map_it->second.end()) { + return nullptr; + } + + return *vec_it; +} + +void SDLDriver::InitJoystick(int joystick_index) { + SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + SDL_GameController* sdl_gamecontroller = nullptr; + + if (SDL_IsGameController(joystick_index)) { + sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + } + + if (!sdl_joystick) { + LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); + return; + } + + const std::string guid = GetGUID(sdl_joystick); + + std::lock_guard lock{joystick_map_mutex}; + if (joystick_map.find(guid) == joystick_map.end()) { + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick_map[guid].emplace_back(std::move(joystick)); + return; + } + + auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = + std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + return; + } + + const int port = static_cast<int>(joystick_guid_list.size()); + auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick_guid_list.emplace_back(std::move(joystick)); +} + +void SDLDriver::CloseJoystick(SDL_Joystick* sdl_joystick) { + const std::string guid = GetGUID(sdl_joystick); + + std::lock_guard lock{joystick_map_mutex}; + // This call to guid is safe since the joystick is guaranteed to be in the map + const auto& joystick_guid_list = joystick_map[guid]; + const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), + [&sdl_joystick](const auto& joystick) { + return joystick->GetSDLJoystick() == sdl_joystick; + }); + + if (joystick_it != joystick_guid_list.end()) { + (*joystick_it)->SetSDLJoystick(nullptr, nullptr); + } +} + +void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { + switch (event.type) { + case SDL_JOYBUTTONUP: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetButton(identifier, event.jbutton.button, false); + } + break; + } + case SDL_JOYBUTTONDOWN: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetButton(identifier, event.jbutton.button, true); + } + break; + } + case SDL_JOYHATMOTION: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetHatButton(identifier, event.jhat.hat, event.jhat.value); + } + break; + } + case SDL_JOYAXISMOTION: { + if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); + } + break; + } + case SDL_CONTROLLERSENSORUPDATE: { + if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { + if (joystick->UpdateMotion(event.csensor)) { + const PadIdentifier identifier = joystick->GetPadIdentifier(); + SetMotion(identifier, 0, joystick->GetMotion()); + } + } + break; + } + case SDL_JOYDEVICEREMOVED: + LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); + CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); + break; + case SDL_JOYDEVICEADDED: + LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); + InitJoystick(event.jdevice.which); + break; + } +} + +void SDLDriver::CloseJoysticks() { + std::lock_guard lock{joystick_map_mutex}; + joystick_map.clear(); +} + +SDLDriver::SDLDriver(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + if (!Settings::values.enable_raw_input) { + // Disable raw input. When enabled this setting causes SDL to die when a web applet opens + SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); + } + + // Prevent SDL from adding undesired axis + SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); + + // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1"); + + // Use hidapi driver for joycons. This will allow joycons to be detected as a GameController and + // not a generic one + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); + + // Disable hidapi driver for xbox. Already default on Windows, this causes conflict with native + // driver on Linux. + SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_XBOX, "0"); + + // If the frontend is going to manage the event loop, then we don't start one here + start_thread = SDL_WasInit(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) == 0; + if (start_thread && SDL_Init(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER) < 0) { + LOG_CRITICAL(Input, "SDL_Init failed with: {}", SDL_GetError()); + return; + } + + SDL_AddEventWatch(&SDLEventWatcher, this); + + initialized = true; + if (start_thread) { + poll_thread = std::thread([this] { + Common::SetCurrentThreadName("yuzu:input:SDL"); + using namespace std::chrono_literals; + while (initialized) { + SDL_PumpEvents(); + std::this_thread::sleep_for(1ms); + } + }); + } + // Because the events for joystick connection happens before we have our event watcher added, we + // can just open all the joysticks right here + for (int i = 0; i < SDL_NumJoysticks(); ++i) { + InitJoystick(i); + } +} + +SDLDriver::~SDLDriver() { + CloseJoysticks(); + SDL_DelEventWatch(&SDLEventWatcher, this); + + initialized = false; + if (start_thread) { + poll_thread.join(); + SDL_QuitSubSystem(SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER); + } +} + +std::vector<Common::ParamPackage> SDLDriver::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs; + for (const auto& [key, value] : joystick_map) { + for (const auto& joystick : value) { + if (!joystick->GetSDLJoystick()) { + continue; + } + const std::string name = + fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + if (joystick->IsJoyconLeft()) { + joycon_pairs.insert_or_assign(joystick->GetPort(), joystick); + } + } + } + + // Add dual controllers + for (const auto& [key, value] : joystick_map) { + for (const auto& joystick : value) { + if (joystick->IsJoyconRight()) { + if (!joycon_pairs.contains(joystick->GetPort())) { + continue; + } + const auto joystick2 = joycon_pairs.at(joystick->GetPort()); + + const std::string name = + fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", GetEngineName()}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"guid2", joystick2->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } + } + } + return devices; +} + +Common::Input::VibrationError SDLDriver::SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) { + const auto joystick = + GetSDLJoystickByGUID(identifier.guid.Format(), static_cast<int>(identifier.port)); + const auto process_amplitude_exp = [](f32 amplitude, f32 factor) { + return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF; + }; + + // Default exponential curve for rumble + f32 factor = 0.35f; + + // If vibration is set as a linear output use a flatter value + if (vibration.type == Common::Input::VibrationAmplificationType::Linear) { + factor = 0.5f; + } + + // Amplitude for HD rumble needs no modification + if (joystick->HasHDRumble()) { + factor = 1.0f; + } + + const Common::Input::VibrationStatus new_vibration{ + .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor), + .low_frequency = vibration.low_frequency, + .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor), + .high_frequency = vibration.high_frequency, + .type = Common::Input::VibrationAmplificationType::Exponential, + }; + + if (!joystick->RumblePlay(new_vibration)) { + return Common::Input::VibrationError::Unknown; + } + + return Common::Input::VibrationError::None; +} + +Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, + s32 axis, float value) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("axis", axis); + params.Set("threshold", "0.5"); + params.Set("invert", value < 0 ? "-" : "+"); + return params; +} + +Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid, + s32 button) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("button", button); + return params; +} + +Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat, + u8 value) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("hat", hat); + params.Set("direction", GetHatButtonName(value)); + return params; +} + +Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const { + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("motion", 0); + params.Set("port", port); + params.Set("guid", std::move(guid)); + return params; +} + +Common::ParamPackage SDLDriver::BuildParamPackageForBinding( + int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const { + switch (binding.bindType) { + case SDL_CONTROLLER_BINDTYPE_NONE: + break; + case SDL_CONTROLLER_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); + case SDL_CONTROLLER_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.value.button); + case SDL_CONTROLLER_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, + static_cast<u8>(binding.value.hat.hat_mask)); + } + return {}; +} + +Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y, float offset_x, + float offset_y) const { + Common::ParamPackage params; + params.Set("engine", GetEngineName()); + params.Set("port", static_cast<int>(identifier.port)); + params.Set("guid", identifier.guid.Format()); + params.Set("axis_x", axis_x); + params.Set("axis_y", axis_y); + params.Set("offset_x", offset_x); + params.Set("offset_y", offset_y); + params.Set("invert_x", "+"); + params.Set("invert_y", "+"); + return params; +} + +ButtonMapping SDLDriver::GetButtonMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. + // We will add those afterwards + // This list also excludes Screenshot since theres not really a mapping for that + ButtonBindings switch_to_sdl_button; + + if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { + switch_to_sdl_button = GetNintendoButtonBinding(joystick); + } else { + switch_to_sdl_button = GetDefaultButtonBinding(); + } + + // Add the missing bindings for ZL/ZR + static constexpr ZButtonBindings switch_to_sdl_axis{{ + {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + }}; + + // Parameters contain two joysticks return dual + if (params.Has("guid2")) { + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + + if (joystick2->GetSDLGameController() != nullptr) { + return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, + switch_to_sdl_axis); + } + } + + return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); +} + +ButtonBindings SDLDriver::GetDefaultButtonBinding() const { + return { + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }; +} + +ButtonBindings SDLDriver::GetNintendoButtonBinding( + const std::shared_ptr<SDLJoystick>& joystick) const { + // Default SL/SR mapping for pro controllers + auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; + auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; + + if (joystick->IsJoyconLeft()) { + sl_button = SDL_CONTROLLER_BUTTON_PADDLE2; + sr_button = SDL_CONTROLLER_BUTTON_PADDLE4; + } + if (joystick->IsJoyconRight()) { + sl_button = SDL_CONTROLLER_BUTTON_PADDLE3; + sr_button = SDL_CONTROLLER_BUTTON_PADDLE1; + } + + return { + std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, + {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, + {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, + {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, + {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, + {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, + {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, + {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, + {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, + {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, + {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, + {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, + {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, + {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, + {Settings::NativeButton::SL, sl_button}, + {Settings::NativeButton::SR, sr_button}, + {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, + }; +} + +ButtonMapping SDLDriver::GetSingleControllerMapping( + const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = joystick->GetSDLGameController(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +ButtonMapping SDLDriver::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, + const std::shared_ptr<SDLJoystick>& joystick2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const { + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + auto* controller = joystick->GetSDLGameController(); + auto* controller2 = joystick2->GetSDLGameController(); + + for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { + if (IsButtonOnLeftSide(switch_button)) { + const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + continue; + } + const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { + if (IsButtonOnLeftSide(switch_button)) { + const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); + continue; + } + const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); + mapping.insert_or_assign( + switch_button, + BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); + } + + return mapping; +} + +bool SDLDriver::IsButtonOnLeftSide(Settings::NativeButton::Values button) const { + switch (button) { + case Settings::NativeButton::DDown: + case Settings::NativeButton::DLeft: + case Settings::NativeButton::DRight: + case Settings::NativeButton::DUp: + case Settings::NativeButton::L: + case Settings::NativeButton::LStick: + case Settings::NativeButton::Minus: + case Settings::NativeButton::Screenshot: + case Settings::NativeButton::ZL: + return true; + default: + return false; + } +} + +AnalogMapping SDLDriver::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + AnalogMapping mapping = {}; + const auto& binding_left_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); + const auto& binding_left_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); + if (params.Has("guid2")) { + const auto identifier = joystick2->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_left_x.value.axis); + PreSetAxis(identifier, binding_left_y.value.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); + const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, + binding_left_y.value.axis, + left_offset_x, left_offset_y)); + } else { + const auto identifier = joystick->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_left_x.value.axis); + PreSetAxis(identifier, binding_left_y.value.axis); + const auto left_offset_x = -GetAxis(identifier, binding_left_x.value.axis); + const auto left_offset_y = -GetAxis(identifier, binding_left_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(identifier, binding_left_x.value.axis, + binding_left_y.value.axis, + left_offset_x, left_offset_y)); + } + const auto& binding_right_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + const auto& binding_right_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + const auto identifier = joystick->GetPadIdentifier(); + PreSetController(identifier); + PreSetAxis(identifier, binding_right_x.value.axis); + PreSetAxis(identifier, binding_right_y.value.axis); + const auto right_offset_x = -GetAxis(identifier, binding_right_x.value.axis); + const auto right_offset_y = -GetAxis(identifier, binding_right_y.value.axis); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(identifier, binding_right_x.value.axis, + binding_right_y.value.axis, right_offset_x, + right_offset_y)); + return mapping; +} + +MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + MotionMapping mapping = {}; + joystick->EnableMotion(); + + if (joystick->HasGyro() || joystick->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, + BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); + } + if (params.Has("guid2")) { + joystick2->EnableMotion(); + if (joystick2->HasGyro() || joystick2->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, + BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); + } + } else { + if (joystick->HasGyro() || joystick->HasAccel()) { + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, + BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); + } + } + + return mapping; +} + +Common::Input::ButtonNames SDLDriver::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + // TODO(German77): Find how to substitue the values for real button names + return Common::Input::ButtonNames::Value; + } + if (params.Has("hat")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return Common::Input::ButtonNames::Value; + } + if (params.Has("motion")) { + return Common::Input::ButtonNames::Engine; + } + + return Common::Input::ButtonNames::Invalid; +} + +std::string SDLDriver::GetHatButtonName(u8 direction_value) const { + switch (direction_value) { + case SDL_HAT_UP: + return "up"; + case SDL_HAT_DOWN: + return "down"; + case SDL_HAT_LEFT: + return "left"; + case SDL_HAT_RIGHT: + return "right"; + default: + return {}; + } +} + +u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const { + Uint8 direction; + if (direction_name == "up") { + direction = SDL_HAT_UP; + } else if (direction_name == "down") { + direction = SDL_HAT_DOWN; + } else if (direction_name == "left") { + direction = SDL_HAT_LEFT; + } else if (direction_name == "right") { + direction = SDL_HAT_RIGHT; + } else { + direction = 0; + } + return direction; +} + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/drivers/sdl_driver.h index 7a9ad6346..e9a5d2e26 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/drivers/sdl_driver.h @@ -5,7 +5,6 @@ #pragma once #include <atomic> -#include <memory> #include <mutex> #include <thread> #include <unordered_map> @@ -13,34 +12,29 @@ #include <SDL.h> #include "common/common_types.h" -#include "common/threadsafe_queue.h" -#include "input_common/sdl/sdl.h" +#include "input_common/input_engine.h" union SDL_Event; using SDL_GameController = struct _SDL_GameController; using SDL_Joystick = struct _SDL_Joystick; using SDL_JoystickID = s32; +namespace InputCommon { + +class SDLJoystick; + using ButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; using ZButtonBindings = std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; -namespace InputCommon::SDL { - -class SDLAnalogFactory; -class SDLButtonFactory; -class SDLMotionFactory; -class SDLVibrationFactory; -class SDLJoystick; - -class SDLState : public State { +class SDLDriver : public InputEngine { public: /// Initializes and registers SDL device factories - SDLState(); + explicit SDLDriver(std::string input_engine_); /// Unregisters SDL device factories and shut them down. - ~SDLState() override; + ~SDLDriver() override; /// Handle SDL_Events for joysticks from SDL_PollEvent void HandleGameControllerEvent(const SDL_Event& event); @@ -54,18 +48,18 @@ public: */ std::shared_ptr<SDLJoystick> GetSDLJoystickByGUID(const std::string& guid, int port); - /// Get all DevicePoller that use the SDL backend for a specific device type - Pollers GetPollers(Polling::DeviceType type) override; - - /// Used by the Pollers during config - std::atomic<bool> polling = false; - Common::SPSCQueue<SDL_Event> event_queue; - - std::vector<Common::ParamPackage> GetInputDevices() override; + 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; + + std::string GetHatButtonName(u8 direction_value) const override; + u8 GetHatButtonId(const std::string& direction_name) const override; + + Common::Input::VibrationError SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus& vibration) override; private: void InitJoystick(int joystick_index); @@ -74,6 +68,23 @@ private: /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); + Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, + float value = 0.1f) const; + Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, + s32 button) const; + + Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, + u8 value) const; + + Common::ParamPackage BuildMotionParam(int port, std::string guid) const; + + Common::ParamPackage BuildParamPackageForBinding( + int port, const std::string& guid, const SDL_GameControllerButtonBind& binding) const; + + Common::ParamPackage BuildParamPackageForAnalog(PadIdentifier identifier, int axis_x, + int axis_y, float offset_x, + float offset_y) const; + /// Returns the default button bindings list for generic controllers ButtonBindings GetDefaultButtonBinding() const; @@ -94,21 +105,13 @@ private: /// Returns true if the button is on the left joycon bool IsButtonOnLeftSide(Settings::NativeButton::Values button) const; - // Set to true if SDL supports game controller subsystem - bool has_gamecontroller = false; - /// Map of GUID of a list of corresponding virtual Joysticks std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; std::mutex joystick_map_mutex; - std::shared_ptr<SDLButtonFactory> button_factory; - std::shared_ptr<SDLAnalogFactory> analog_factory; - std::shared_ptr<SDLVibrationFactory> vibration_factory; - std::shared_ptr<SDLMotionFactory> motion_factory; - bool start_thread = false; std::atomic<bool> initialized = false; std::thread poll_thread; }; -} // namespace InputCommon::SDL +} // namespace InputCommon diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp new file mode 100644 index 000000000..5bdd5dac3 --- /dev/null +++ b/src/input_common/drivers/tas_input.cpp @@ -0,0 +1,320 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <cstring> +#include <fmt/format.h> + +#include "common/fs/file.h" +#include "common/fs/fs_types.h" +#include "common/fs/path_util.h" +#include "common/logging/log.h" +#include "common/settings.h" +#include "input_common/drivers/tas_input.h" + +namespace InputCommon::TasInput { + +enum class Tas::TasAxis : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + Undefined, +}; + +// Supported keywords and buttons from a TAS file +constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { + std::pair{"KEY_A", TasButton::BUTTON_A}, + {"KEY_B", TasButton::BUTTON_B}, + {"KEY_X", TasButton::BUTTON_X}, + {"KEY_Y", TasButton::BUTTON_Y}, + {"KEY_LSTICK", TasButton::STICK_L}, + {"KEY_RSTICK", TasButton::STICK_R}, + {"KEY_L", TasButton::TRIGGER_L}, + {"KEY_R", TasButton::TRIGGER_R}, + {"KEY_PLUS", TasButton::BUTTON_PLUS}, + {"KEY_MINUS", TasButton::BUTTON_MINUS}, + {"KEY_DLEFT", TasButton::BUTTON_LEFT}, + {"KEY_DUP", TasButton::BUTTON_UP}, + {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, + {"KEY_DDOWN", TasButton::BUTTON_DOWN}, + {"KEY_SL", TasButton::BUTTON_SL}, + {"KEY_SR", TasButton::BUTTON_SR}, + {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, + {"KEY_HOME", TasButton::BUTTON_HOME}, + {"KEY_ZL", TasButton::TRIGGER_ZL}, + {"KEY_ZR", TasButton::TRIGGER_ZR}, +}; + +Tas::Tas(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) { + PadIdentifier identifier{ + .guid = Common::UUID{}, + .port = player_index, + .pad = 0, + }; + PreSetController(identifier); + } + ClearInput(); + if (!Settings::values.tas_enable) { + needs_reset = true; + return; + } + LoadTasFiles(); +} + +Tas::~Tas() { + Stop(); +} + +void Tas::LoadTasFiles() { + script_length = 0; + for (size_t i = 0; i < commands.size(); i++) { + LoadTasFile(i, 0); + if (commands[i].size() > script_length) { + script_length = commands[i].size(); + } + } +} + +void Tas::LoadTasFile(size_t player_index, size_t file_index) { + commands[player_index].clear(); + + std::string file = Common::FS::ReadStringFromFile( + Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / + fmt::format("script{}-{}.txt", file_index, player_index + 1), + Common::FS::FileType::BinaryFile); + std::istringstream command_line(file); + std::string line; + int frame_no = 0; + while (std::getline(command_line, line, '\n')) { + if (line.empty()) { + continue; + } + + std::vector<std::string> seg_list; + { + std::istringstream line_stream(line); + std::string segment; + while (std::getline(line_stream, segment, ' ')) { + seg_list.push_back(std::move(segment)); + } + } + + if (seg_list.size() < 4) { + continue; + } + + const auto num_frames = std::stoi(seg_list[0]); + while (frame_no < num_frames) { + commands[player_index].emplace_back(); + frame_no++; + } + + TASCommand command = { + .buttons = ReadCommandButtons(seg_list[1]), + .l_axis = ReadCommandAxis(seg_list[2]), + .r_axis = ReadCommandAxis(seg_list[3]), + }; + commands[player_index].push_back(command); + frame_no++; + } + LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); +} + +void Tas::WriteTasFile(std::u8string_view file_name) { + std::string output_text; + for (size_t frame = 0; frame < record_commands.size(); frame++) { + const TASCommand& line = record_commands[frame]; + output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons), + WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis)); + } + + const auto tas_file_name = Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name; + const auto bytes_written = + Common::FS::WriteStringToFile(tas_file_name, Common::FS::FileType::TextFile, output_text); + if (bytes_written == output_text.size()) { + LOG_INFO(Input, "TAS file written to file!"); + } else { + LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, + output_text.size()); + } +} + +void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) { + last_input = { + .buttons = buttons, + .l_axis = left_axis, + .r_axis = right_axis, + }; +} + +std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { + TasState state; + if (is_recording) { + return {TasState::Recording, 0, record_commands.size()}; + } + + if (is_running) { + state = TasState::Running; + } else { + state = TasState::Stopped; + } + + return {state, current_command, script_length}; +} + +void Tas::UpdateThread() { + if (!Settings::values.tas_enable) { + if (is_running) { + Stop(); + } + return; + } + + if (is_recording) { + record_commands.push_back(last_input); + } + if (needs_reset) { + current_command = 0; + needs_reset = false; + LoadTasFiles(); + LOG_DEBUG(Input, "tas_reset done"); + } + + if (!is_running) { + ClearInput(); + return; + } + if (current_command < script_length) { + LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); + const size_t frame = current_command++; + for (size_t player_index = 0; player_index < commands.size(); player_index++) { + TASCommand command{}; + if (frame < commands[player_index].size()) { + command = commands[player_index][frame]; + } + + PadIdentifier identifier{ + .guid = Common::UUID{}, + .port = player_index, + .pad = 0, + }; + for (std::size_t i = 0; i < sizeof(command.buttons) * 8; ++i) { + const bool button_status = (command.buttons & (1LLU << i)) != 0; + const int button = static_cast<int>(i); + SetButton(identifier, button, button_status); + } + SetTasAxis(identifier, TasAxis::StickX, command.l_axis.x); + SetTasAxis(identifier, TasAxis::StickY, command.l_axis.y); + SetTasAxis(identifier, TasAxis::SubstickX, command.r_axis.x); + SetTasAxis(identifier, TasAxis::SubstickY, command.r_axis.y); + } + } else { + is_running = Settings::values.tas_loop.GetValue(); + LoadTasFiles(); + current_command = 0; + ClearInput(); + } +} + +void Tas::ClearInput() { + ResetButtonState(); + ResetAnalogState(); +} + +TasAnalog Tas::ReadCommandAxis(const std::string& line) const { + std::vector<std::string> seg_list; + { + std::istringstream line_stream(line); + std::string segment; + while (std::getline(line_stream, segment, ';')) { + seg_list.push_back(std::move(segment)); + } + } + + const float x = std::stof(seg_list.at(0)) / 32767.0f; + const float y = std::stof(seg_list.at(1)) / 32767.0f; + + return {x, y}; +} + +u64 Tas::ReadCommandButtons(const std::string& line) const { + std::istringstream button_text(line); + std::string button_line; + u64 buttons = 0; + while (std::getline(button_text, button_line, ';')) { + for (const auto& [text, tas_button] : text_to_tas_button) { + if (text == button_line) { + buttons |= static_cast<u64>(tas_button); + break; + } + } + } + return buttons; +} + +std::string Tas::WriteCommandButtons(u64 buttons) const { + std::string returns; + for (const auto& [text_button, tas_button] : text_to_tas_button) { + if ((buttons & static_cast<u64>(tas_button)) != 0) { + returns += fmt::format("{};", text_button); + } + } + return returns.empty() ? "NONE" : returns; +} + +std::string Tas::WriteCommandAxis(TasAnalog analog) const { + return fmt::format("{};{}", analog.x * 32767, analog.y * 32767); +} + +void Tas::SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value) { + SetAxis(identifier, static_cast<int>(axis), value); +} + +void Tas::StartStop() { + if (!Settings::values.tas_enable) { + return; + } + if (is_running) { + Stop(); + } else { + is_running = true; + } +} + +void Tas::Stop() { + is_running = false; +} + +void Tas::Reset() { + if (!Settings::values.tas_enable) { + return; + } + needs_reset = true; +} + +bool Tas::Record() { + if (!Settings::values.tas_enable) { + return true; + } + is_recording = !is_recording; + return is_recording; +} + +void Tas::SaveRecording(bool overwrite_file) { + if (is_recording) { + return; + } + if (record_commands.empty()) { + return; + } + WriteTasFile(u8"record.txt"); + if (overwrite_file) { + WriteTasFile(u8"script0-1.txt"); + } + needs_reset = true; + record_commands.clear(); +} + +} // namespace InputCommon::TasInput diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h new file mode 100644 index 000000000..4b4e6c417 --- /dev/null +++ b/src/input_common/drivers/tas_input.h @@ -0,0 +1,201 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include <vector> + +#include "common/common_types.h" +#include "input_common/input_engine.h" + +/* +To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below +Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt +for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). + +A script file has the same format as TAS-nx uses, so final files will look like this: + +1 KEY_B 0;0 0;0 +6 KEY_ZL 0;0 0;0 +41 KEY_ZL;KEY_Y 0;0 0;0 +43 KEY_X;KEY_A 32767;0 0;0 +44 KEY_A 32767;0 0;0 +45 KEY_A 32767;0 0;0 +46 KEY_A 32767;0 0;0 +47 KEY_A 32767;0 0;0 + +After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey +CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file +has. Playback can be started or stopped using CTRL+F5. + +However, for playback to actually work, the correct input device has to be selected: In the Controls +menu, select TAS from the device list for the controller that the script should be played on. + +Recording a new script file is really simple: Just make sure that the proper device (not TAS) is +connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke +again (CTRL+F7). The new script will be saved at the location previously selected, as the filename +record.txt. + +For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller +P1). +*/ + +namespace InputCommon::TasInput { + +constexpr size_t PLAYER_NUMBER = 10; + +enum class TasButton : u64 { + BUTTON_A = 1U << 0, + BUTTON_B = 1U << 1, + BUTTON_X = 1U << 2, + BUTTON_Y = 1U << 3, + STICK_L = 1U << 4, + STICK_R = 1U << 5, + TRIGGER_L = 1U << 6, + TRIGGER_R = 1U << 7, + TRIGGER_ZL = 1U << 8, + TRIGGER_ZR = 1U << 9, + BUTTON_PLUS = 1U << 10, + BUTTON_MINUS = 1U << 11, + BUTTON_LEFT = 1U << 12, + BUTTON_UP = 1U << 13, + BUTTON_RIGHT = 1U << 14, + BUTTON_DOWN = 1U << 15, + BUTTON_SL = 1U << 16, + BUTTON_SR = 1U << 17, + BUTTON_HOME = 1U << 18, + BUTTON_CAPTURE = 1U << 19, +}; + +struct TasAnalog { + float x{}; + float y{}; +}; + +enum class TasState { + Running, + Recording, + Stopped, +}; + +class Tas final : public InputEngine { +public: + explicit Tas(std::string input_engine_); + ~Tas() override; + + /** + * Changes the input status that will be stored in each frame + * @param buttons Bitfield with the status of the buttons + * @param left_axis Value of the left axis + * @param right_axis Value of the right axis + */ + void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis); + + // Main loop that records or executes input + void UpdateThread(); + + // Sets the flag to start or stop the TAS command execution and swaps controllers profiles + void StartStop(); + + // Stop the TAS and reverts any controller profile + void Stop(); + + // Sets the flag to reload the file and start from the beginning in the next update + void Reset(); + + /** + * Sets the flag to enable or disable recording of inputs + * @returns true if the current recording status is enabled + */ + bool Record(); + + /** + * Saves contents of record_commands on a file + * @param overwrite_file Indicates if player 1 should be overwritten + */ + void SaveRecording(bool overwrite_file); + + /** + * Returns the current status values of TAS playback/recording + * @returns A Tuple of + * TasState indicating the current state out of Running ; + * Current playback progress ; + * Total length of script file currently loaded or being recorded + */ + std::tuple<TasState, size_t, size_t> GetStatus() const; + +private: + enum class TasAxis : u8; + + struct TASCommand { + u64 buttons{}; + TasAnalog l_axis{}; + TasAnalog r_axis{}; + }; + + /// Loads TAS files from all players + void LoadTasFiles(); + + /** + * Loads TAS file from the specified player + * @param player_index Player number to save the script + * @param file_index Script number of the file + */ + void LoadTasFile(size_t player_index, size_t file_index); + + /** + * Writes a TAS file from the recorded commands + * @param file_name Name of the file to be written + */ + void WriteTasFile(std::u8string_view file_name); + + /** + * Parses a string containing the axis values. X and Y have a range from -32767 to 32767 + * @param line String containing axis values with the following format "x;y" + * @returns A TAS analog object with axis values with range from -1.0 to 1.0 + */ + TasAnalog ReadCommandAxis(const std::string& line) const; + + /** + * Parses a string containing the button values. Each button is represented by it's text format + * specified in text_to_tas_button array + * @param line string containing button name with the following format "a;b;c;d..." + * @returns A u64 with each bit representing the status of a button + */ + u64 ReadCommandButtons(const std::string& line) const; + + /** + * Reset state of all players + */ + void ClearInput(); + + /** + * Converts an u64 containing the button status into the text equivalent + * @param buttons Bitfield with the status of the buttons + * @returns A string with the name of the buttons to be written to the file + */ + std::string WriteCommandButtons(u64 buttons) const; + + /** + * Converts an TAS analog object containing the axis status into the text equivalent + * @param analog Value of the axis + * @returns A string with the value of the axis to be written to the file + */ + std::string WriteCommandAxis(TasAnalog analog) const; + + /// Sets an axis for a particular pad to the given value. + void SetTasAxis(const PadIdentifier& identifier, TasAxis axis, f32 value); + + size_t script_length{0}; + bool is_recording{false}; + bool is_running{false}; + bool needs_reset{false}; + std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{}; + std::vector<TASCommand> record_commands{}; + size_t current_command{0}; + TASCommand last_input{}; // only used for recording +}; +} // namespace InputCommon::TasInput diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp new file mode 100644 index 000000000..880781825 --- /dev/null +++ b/src/input_common/drivers/touch_screen.cpp @@ -0,0 +1,53 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/param_package.h" +#include "input_common/drivers/touch_screen.h" + +namespace InputCommon { + +constexpr PadIdentifier identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; + +TouchScreen::TouchScreen(std::string input_engine_) : InputEngine(std::move(input_engine_)) { + PreSetController(identifier); +} + +void TouchScreen::TouchMoved(float x, float y, std::size_t finger) { + if (finger >= 16) { + return; + } + TouchPressed(x, y, finger); +} + +void TouchScreen::TouchPressed(float x, float y, std::size_t finger) { + if (finger >= 16) { + return; + } + SetButton(identifier, static_cast<int>(finger), true); + SetAxis(identifier, static_cast<int>(finger * 2), x); + SetAxis(identifier, static_cast<int>(finger * 2 + 1), y); +} + +void TouchScreen::TouchReleased(std::size_t finger) { + if (finger >= 16) { + return; + } + SetButton(identifier, static_cast<int>(finger), false); + SetAxis(identifier, static_cast<int>(finger * 2), 0.0f); + SetAxis(identifier, static_cast<int>(finger * 2 + 1), 0.0f); +} + +void TouchScreen::ReleaseAllTouch() { + for (int index = 0; index < 16; ++index) { + SetButton(identifier, index, false); + SetAxis(identifier, index * 2, 0.0f); + SetAxis(identifier, index * 2 + 1, 0.0f); + } +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h new file mode 100644 index 000000000..bf395c40b --- /dev/null +++ b/src/input_common/drivers/touch_screen.h @@ -0,0 +1,44 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "input_common/input_engine.h" + +namespace InputCommon { + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class TouchScreen final : public InputEngine { +public: + explicit TouchScreen(std::string input_engine_); + + /** + * Signals that mouse has moved. + * @param x the x-coordinate of the cursor + * @param y the y-coordinate of the cursor + * @param center_x the x-coordinate of the middle of the screen + * @param center_y the y-coordinate of the middle of the screen + */ + void TouchMoved(float x, float y, std::size_t finger); + + /** + * Sets the status of all buttons bound with the key to pressed + * @param key_code the code of the key to press + */ + void TouchPressed(float x, float y, std::size_t finger); + + /** + * Sets the status of all buttons bound with the key to released + * @param key_code the code of the key to release + */ + void TouchReleased(std::size_t finger); + + /// Resets all inputs to their initial value + void ReleaseAllTouch(); +}; + +} // namespace InputCommon diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp new file mode 100644 index 000000000..4ab991a7d --- /dev/null +++ b/src/input_common/drivers/udp_client.cpp @@ -0,0 +1,591 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#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; +} + +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); + } +} + +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); + } +} + +const 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, + }; +} + +const Common::UUID UDPClient::GetHostUUID(const std::string host) const { + const auto ip = boost::asio::ip::address_v4::from_string(host); + const auto hex_host = fmt::format("{:06x}", ip.to_ulong()); + 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.Format()); + 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>, 18> + 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::SL, PadButton::L2}, + {Settings::NativeButton::SR, PadButton::R2}, + {Settings::NativeButton::LStick, PadButton::L3}, + {Settings::NativeButton::RStick, PadButton::R3}, + }; + 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 motion_params; + motion_params.Set("engine", GetEngineName()); + motion_params.Set("guid", params.Get("guid", "")); + motion_params.Set("port", params.Get("port", 0)); + motion_params.Set("pad", params.Get("pad", 0)); + motion_params.Set("motion", 0); + mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, std::move(motion_params)); + mapping.insert_or_assign(Settings::NativeMotion::MotionRight, std::move(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; + 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; +} + +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] { + Status current_status{Status::Initialized}; + SocketCallback callback{ + [](Response::Version) {}, [](Response::PortInfo) {}, + [&](Response::PadData data) { + static constexpr u16 CALIBRATION_THRESHOLD = 100; + static constexpr u16 MAX_VALUE = UINT16_MAX; + + if (current_status == Status::Initialized) { + // Receiving data means the communication is ready now + current_status = Status::Ready; + status_callback(current_status); + } + const auto& touchpad_0 = data.touch[0]; + if (touchpad_0.is_active == 0) { + return; + } + LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); + const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x)); + const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y)); + if (current_status == Status::Ready) { + // First touch - min data (min_x/min_y) + current_status = Status::Stage1Completed; + status_callback(current_status); + } + if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && + touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { + // Set the current position as max value and finishes configuration + const u16 max_x = touchpad_0.x; + const u16 max_y = touchpad_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 diff --git a/src/input_common/udp/client.h b/src/input_common/drivers/udp_client.h index 380f9bb76..1adc947c4 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/drivers/udp_client.h @@ -4,20 +4,11 @@ #pragma once -#include <functional> -#include <memory> -#include <mutex> #include <optional> -#include <string> -#include <thread> -#include <tuple> + #include "common/common_types.h" -#include "common/param_package.h" #include "common/thread.h" -#include "common/threadsafe_queue.h" -#include "common/vector_math.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" +#include "input_common/input_engine.h" namespace InputCommon::CemuhookUDP { @@ -30,16 +21,6 @@ struct TouchPad; struct Version; } // namespace Response -enum class PadMotion { - GyroX, - GyroY, - GyroZ, - AccX, - AccY, - AccZ, - Undefined, -}; - enum class PadTouch { Click, Undefined, @@ -49,14 +30,10 @@ struct UDPPadStatus { std::string host{"127.0.0.1"}; u16 port{26760}; std::size_t pad_index{}; - PadMotion motion{PadMotion::Undefined}; - f32 motion_value{0.0f}; }; struct DeviceStatus { std::mutex update_mutex; - Input::MotionStatus motion_status; - std::tuple<float, float, bool> touch_status; // calibration data for scaling the device's touch area to 3ds struct CalibrationData { @@ -68,48 +45,85 @@ struct DeviceStatus { std::optional<CalibrationData> touch_calibration; }; -class Client { +/** + * 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: - // Initialize the UDP client capture and read sequence - Client(); - - // Close and release the client - ~Client(); - - // Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - std::vector<Common::ParamPackage> GetInputDevices() const; + explicit UDPClient(std::string input_engine_); + ~UDPClient() override; - bool DeviceConnected(std::size_t pad) const; void ReloadSockets(); - Common::SPSCQueue<UDPPadStatus>& GetPadQueue(); - const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const; + /// 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; - DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad); - const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const; +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, + }; - Input::TouchStatus& GetTouchState(); - const Input::TouchStatus& GetTouchState() const; + 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, + }; -private: struct PadData { std::size_t pad_index{}; bool connected{}; DeviceStatus status; u64 packet_sequence{}; - // Realtime values - // motion is initalized with PID values for drift correction on joycons - InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; std::chrono::time_point<std::chrono::steady_clock> last_update; }; struct ClientConnection { ClientConnection(); ~ClientConnection(); + Common::UUID uuid{"7F000001"}; std::string host{"127.0.0.1"}; u16 port{26760}; s8 active{-1}; @@ -127,28 +141,16 @@ private: void OnPortInfo(Response::PortInfo); void OnPadData(Response::PadData, std::size_t client); void StartCommunication(std::size_t client, const std::string& host, u16 port); - void UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro); - - // Returns an unused finger id, if there is no fingers available std::nullopt will be - // returned - std::optional<std::size_t> GetUnusedFingerID() const; - - // Merges and updates all touch inputs into the touch_status array - void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id); + const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; + const Common::UUID GetHostUUID(const std::string host) const; - bool configuring = false; + 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; - // Each client can have up 2 touch inputs - static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2; std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{}; std::array<ClientConnection, MAX_UDP_CLIENTS> clients{}; - Common::SPSCQueue<UDPPadStatus> pad_queue{}; - Input::TouchStatus touch_status{}; - std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{}; }; /// An async job allowing configuration of the touchpad calibration. diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h deleted file mode 100644 index e5de5e94f..000000000 --- a/src/input_common/gcadapter/gc_adapter.h +++ /dev/null @@ -1,168 +0,0 @@ -// Copyright 2014 Dolphin Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#pragma once -#include <algorithm> -#include <functional> -#include <mutex> -#include <thread> -#include <unordered_map> -#include "common/common_types.h" -#include "common/threadsafe_queue.h" -#include "input_common/main.h" - -struct libusb_context; -struct libusb_device; -struct libusb_device_handle; - -namespace GCAdapter { - -enum class PadButton { - Undefined = 0x0000, - ButtonLeft = 0x0001, - ButtonRight = 0x0002, - ButtonDown = 0x0004, - ButtonUp = 0x0008, - TriggerZ = 0x0010, - TriggerR = 0x0020, - TriggerL = 0x0040, - ButtonA = 0x0100, - ButtonB = 0x0200, - ButtonX = 0x0400, - ButtonY = 0x0800, - ButtonStart = 0x1000, - // Below is for compatibility with "AxisButton" type - Stick = 0x2000, -}; - -enum class PadAxes : u8 { - StickX, - StickY, - SubstickX, - SubstickY, - TriggerLeft, - TriggerRight, - Undefined, -}; - -enum class ControllerTypes { - None, - Wired, - Wireless, -}; - -struct GCPadStatus { - std::size_t port{}; - - PadButton button{PadButton::Undefined}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits - - PadAxes axis{PadAxes::Undefined}; - s16 axis_value{}; - u8 axis_threshold{50}; -}; - -struct GCController { - ControllerTypes type{}; - bool enable_vibration{}; - u8 rumble_amplitude{}; - u16 buttons{}; - PadButton last_button{}; - std::array<s16, 6> axis_values{}; - std::array<u8, 6> axis_origin{}; - u8 reset_origin_counter{}; -}; - -class Adapter { -public: - Adapter(); - ~Adapter(); - - /// Request a vibration for a controller - bool RumblePlay(std::size_t port, u8 amplitude); - - /// Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - Common::SPSCQueue<GCPadStatus>& GetPadQueue(); - const Common::SPSCQueue<GCPadStatus>& GetPadQueue() const; - - GCController& GetPadState(std::size_t port); - const GCController& GetPadState(std::size_t port) const; - - /// Returns true if there is a device connected to port - bool DeviceConnected(std::size_t port) const; - - /// Used for automapping features - std::vector<Common::ParamPackage> GetInputDevices() const; - InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; - InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; - -private: - using AdapterPayload = std::array<u8, 37>; - - void UpdatePadType(std::size_t port, ControllerTypes pad_type); - void UpdateControllers(const AdapterPayload& adapter_payload); - void UpdateYuzuSettings(std::size_t port); - void UpdateStateButtons(std::size_t port, u8 b1, u8 b2); - void UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload); - void UpdateVibrations(); - - void AdapterInputThread(); - - void AdapterScanThread(); - - bool IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size); - - // Updates vibration state of all controllers - void SendVibrations(); - - /// For use in initialization, querying devices to find the adapter - void Setup(); - - /// Resets status of all GC controller devices to a disconnected state - void ResetDevices(); - - /// Resets status of device connected to a disconnected state - void ResetDevice(std::size_t port); - - /// Returns true if we successfully gain access to GC Adapter - bool CheckDeviceAccess(); - - /// Captures GC Adapter endpoint address - /// Returns true if the endpoint was set correctly - bool GetGCEndpoint(libusb_device* device); - - /// For shutting down, clear all data, join all threads, release usb - void Reset(); - - // Join all threads - void JoinThreads(); - - // Release usb handles - void ClearLibusbHandle(); - - libusb_device_handle* usb_adapter_handle = nullptr; - std::array<GCController, 4> pads; - Common::SPSCQueue<GCPadStatus> pad_queue; - - std::thread adapter_input_thread; - std::thread adapter_scan_thread; - bool adapter_input_thread_running; - bool adapter_scan_thread_running; - bool restart_scan_thread; - - libusb_context* libusb_ctx; - - u8 input_endpoint{0}; - u8 output_endpoint{0}; - u8 input_error_counter{0}; - u8 output_error_counter{0}; - int vibration_counter{0}; - - bool configuring{false}; - bool rumble_enabled{true}; - bool vibration_changed{true}; -}; -} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp deleted file mode 100644 index 1b6ded8d6..000000000 --- a/src/input_common/gcadapter/gc_poller.cpp +++ /dev/null @@ -1,356 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <atomic> -#include <list> -#include <mutex> -#include <utility> -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/gcadapter/gc_adapter.h" -#include "input_common/gcadapter/gc_poller.h" - -namespace InputCommon { - -class GCButton final : public Input::ButtonDevice { -public: - explicit GCButton(u32 port_, s32 button_, const GCAdapter::Adapter* adapter) - : port(port_), button(button_), gcadapter(adapter) {} - - ~GCButton() override; - - bool GetStatus() const override { - if (gcadapter->DeviceConnected(port)) { - return (gcadapter->GetPadState(port).buttons & button) != 0; - } - return false; - } - -private: - const u32 port; - const s32 button; - const GCAdapter::Adapter* gcadapter; -}; - -class GCAxisButton final : public Input::ButtonDevice { -public: - explicit GCAxisButton(u32 port_, u32 axis_, float threshold_, bool trigger_if_greater_, - const GCAdapter::Adapter* adapter) - : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), - gcadapter(adapter) {} - - bool GetStatus() const override { - if (gcadapter->DeviceConnected(port)) { - const float current_axis_value = gcadapter->GetPadState(port).axis_values.at(axis); - const float axis_value = current_axis_value / 128.0f; - if (trigger_if_greater) { - // TODO: Might be worthwile to set a slider for the trigger threshold. It is - // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick - return axis_value > threshold; - } - return axis_value < -threshold; - } - return false; - } - -private: - const u32 port; - const u32 axis; - float threshold; - bool trigger_if_greater; - const GCAdapter::Adapter* gcadapter; -}; - -GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) - : adapter(std::move(adapter_)) {} - -GCButton::~GCButton() = default; - -std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - const auto port = static_cast<u32>(params.Get("port", 0)); - - constexpr s32 PAD_STICK_ID = static_cast<s32>(GCAdapter::PadButton::Stick); - - // button is not an axis/stick button - if (button_id != PAD_STICK_ID) { - return std::make_unique<GCButton>(port, button_id, adapter.get()); - } - - // For Axis buttons, used by the binary sticks. - if (button_id == PAD_STICK_ID) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.25f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater, - adapter.get()); - } - - return nullptr; -} - -Common::ParamPackage GCButtonFactory::GetNextInput() const { - Common::ParamPackage params; - GCAdapter::GCPadStatus pad; - auto& queue = adapter->GetPadQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - params.Set("engine", "gcpad"); - params.Set("port", static_cast<s32>(pad.port)); - if (pad.button != GCAdapter::PadButton::Undefined) { - params.Set("button", static_cast<u16>(pad.button)); - } - - // For Axis button implementation - if (pad.axis != GCAdapter::PadAxes::Undefined) { - params.Set("axis", static_cast<u8>(pad.axis)); - params.Set("button", static_cast<u16>(GCAdapter::PadButton::Stick)); - params.Set("threshold", "0.25"); - if (pad.axis_value > 0) { - params.Set("direction", "+"); - } else { - params.Set("direction", "-"); - } - break; - } - } - return params; -} - -void GCButtonFactory::BeginConfiguration() { - polling = true; - adapter->BeginConfiguration(); -} - -void GCButtonFactory::EndConfiguration() { - polling = false; - adapter->EndConfiguration(); -} - -class GCAnalog final : public Input::AnalogDevice { -public: - explicit GCAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, - float deadzone_, float range_, const GCAdapter::Adapter* adapter) - : port(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), - deadzone(deadzone_), range(range_), gcadapter(adapter) {} - - float GetAxis(u32 axis) const { - if (gcadapter->DeviceConnected(port)) { - std::lock_guard lock{mutex}; - const auto axis_value = - static_cast<float>(gcadapter->GetPadState(port).axis_values.at(axis)); - return (axis_value) / (100.0f * range); - } - return 0.0f; - } - - std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - if (invert_x) { - x = -x; - } - if (invert_y) { - y = -y; - } - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple<float, float> GetStatus() const override { - const auto [x, y] = GetAnalog(axis_x, axis_y); - const float r = std::sqrt((x * x) + (y * y)); - if (r > deadzone) { - return {x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)}; - } - return {0.0f, 0.0f}; - } - - std::tuple<float, float> GetRawStatus() const override { - const float x = GetAxis(axis_x); - const float y = GetAxis(axis_y); - return {x, y}; - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {deadzone, range, 0.5f}; - } - - bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { - const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.5f; - switch (direction) { - case Input::AnalogDirection::RIGHT: - return x > directional_deadzone; - case Input::AnalogDirection::LEFT: - return x < -directional_deadzone; - case Input::AnalogDirection::UP: - return y > directional_deadzone; - case Input::AnalogDirection::DOWN: - return y < -directional_deadzone; - } - return false; - } - -private: - const u32 port; - const u32 axis_x; - const u32 axis_y; - const bool invert_x; - const bool invert_y; - const float deadzone; - const float range; - const GCAdapter::Adapter* gcadapter; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) - : adapter(std::move(adapter_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { - const auto port = static_cast<u32>(params.Get("port", 0)); - const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); - const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); - const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - const std::string invert_x_value = params.Get("invert_x", "+"); - const std::string invert_y_value = params.Get("invert_y", "+"); - const bool invert_x = invert_x_value == "-"; - const bool invert_y = invert_y_value == "-"; - - return std::make_unique<GCAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, - adapter.get()); -} - -void GCAnalogFactory::BeginConfiguration() { - polling = true; - adapter->BeginConfiguration(); -} - -void GCAnalogFactory::EndConfiguration() { - polling = false; - adapter->EndConfiguration(); -} - -Common::ParamPackage GCAnalogFactory::GetNextInput() { - GCAdapter::GCPadStatus pad; - Common::ParamPackage params; - auto& queue = adapter->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.button != GCAdapter::PadButton::Undefined) { - params.Set("engine", "gcpad"); - params.Set("port", static_cast<s32>(pad.port)); - params.Set("button", static_cast<u16>(pad.button)); - return params; - } - if (pad.axis == GCAdapter::PadAxes::Undefined || - std::abs(static_cast<float>(pad.axis_value) / 128.0f) < 0.1f) { - continue; - } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second input event. The axes also must be from the same joystick. - const u8 axis = static_cast<u8>(pad.axis); - if (axis == 0 || axis == 1) { - analog_x_axis = 0; - analog_y_axis = 1; - controller_number = static_cast<s32>(pad.port); - break; - } - if (axis == 2 || axis == 3) { - analog_x_axis = 2; - analog_y_axis = 3; - controller_number = static_cast<s32>(pad.port); - break; - } - - if (analog_x_axis == -1) { - analog_x_axis = axis; - controller_number = static_cast<s32>(pad.port); - } else if (analog_y_axis == -1 && analog_x_axis != axis && - controller_number == static_cast<s32>(pad.port)) { - analog_y_axis = axis; - break; - } - } - if (analog_x_axis != -1 && analog_y_axis != -1) { - params.Set("engine", "gcpad"); - params.Set("port", controller_number); - params.Set("axis_x", analog_x_axis); - params.Set("axis_y", analog_y_axis); - params.Set("invert_x", "+"); - params.Set("invert_y", "+"); - analog_x_axis = -1; - analog_y_axis = -1; - controller_number = -1; - return params; - } - return params; -} - -class GCVibration final : public Input::VibrationDevice { -public: - explicit GCVibration(u32 port_, GCAdapter::Adapter* adapter) - : port(port_), gcadapter(adapter) {} - - u8 GetStatus() const override { - return gcadapter->RumblePlay(port, 0); - } - - bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, - [[maybe_unused]] f32 freq_high) const override { - const auto mean_amplitude = (amp_low + amp_high) * 0.5f; - const auto processed_amplitude = - static_cast<u8>((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); - - return gcadapter->RumblePlay(port, processed_amplitude); - } - -private: - const u32 port; - GCAdapter::Adapter* gcadapter; -}; - -/// An vibration device factory that creates vibration devices from GC Adapter -GCVibrationFactory::GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) - : adapter(std::move(adapter_)) {} - -/** - * Creates a vibration device from a joystick - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - */ -std::unique_ptr<Input::VibrationDevice> GCVibrationFactory::Create( - const Common::ParamPackage& params) { - const auto port = static_cast<u32>(params.Get("port", 0)); - - return std::make_unique<GCVibration>(port, adapter.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h deleted file mode 100644 index d1271e3ea..000000000 --- a/src/input_common/gcadapter/gc_poller.h +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "core/frontend/input.h" -#include "input_common/gcadapter/gc_adapter.h" - -namespace InputCommon { - -/** - * A button device factory representing a gcpad. It receives gcpad events and forward them - * to all button devices it created. - */ -class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> { -public: - explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); - - /** - * Creates a button device from a button press - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<GCAdapter::Adapter> adapter; - bool polling = false; -}; - -/// An analog device factory that creates analog devices from GC Adapter -class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> { -public: - explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); - - std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<GCAdapter::Adapter> adapter; - int analog_x_axis = -1; - int analog_y_axis = -1; - int controller_number = -1; - bool polling = false; -}; - -/// A vibration device factory creates vibration devices from GC Adapter -class GCVibrationFactory final : public Input::Factory<Input::VibrationDevice> { -public: - explicit GCVibrationFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); - - std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override; - -private: - std::shared_ptr<GCAdapter::Adapter> adapter; -}; - -} // namespace InputCommon diff --git a/src/input_common/helpers/stick_from_buttons.cpp b/src/input_common/helpers/stick_from_buttons.cpp new file mode 100644 index 000000000..e23394f5f --- /dev/null +++ b/src/input_common/helpers/stick_from_buttons.cpp @@ -0,0 +1,317 @@ +// Copyright 2017 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <chrono> +#include <cmath> +#include "common/math_util.h" +#include "common/settings.h" +#include "input_common/helpers/stick_from_buttons.h" + +namespace InputCommon { + +class Stick final : public Common::Input::InputDevice { +public: + using Button = std::unique_ptr<Common::Input::InputDevice>; + + Stick(Button up_, Button down_, Button left_, Button right_, Button modifier_, + float modifier_scale_, float modifier_angle_) + : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), + right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), + modifier_angle(modifier_angle_) { + up->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateUpButtonStatus(callback_); + }, + }); + down->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateDownButtonStatus(callback_); + }, + }); + left->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateLeftButtonStatus(callback_); + }, + }); + right->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateRightButtonStatus(callback_); + }, + }); + modifier->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateModButtonStatus(callback_); + }, + }); + last_x_axis_value = 0.0f; + last_y_axis_value = 0.0f; + } + + bool IsAngleGreater(float old_angle, float new_angle) const { + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float top_limit = new_angle + aperture; + return (old_angle > new_angle && old_angle <= top_limit) || + (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); + } + + bool IsAngleSmaller(float old_angle, float new_angle) const { + constexpr float TAU = Common::PI * 2.0f; + // Use wider angle to ease the transition. + constexpr float aperture = TAU * 0.15f; + const float bottom_limit = new_angle - aperture; + return (old_angle >= bottom_limit && old_angle < new_angle) || + (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); + } + + float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { + constexpr float TAU = Common::PI * 2.0f; + float new_angle = angle; + + auto time_difference = static_cast<float>( + std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); + time_difference /= 1000.0f * 1000.0f; + if (time_difference > 0.5f) { + time_difference = 0.5f; + } + + if (IsAngleGreater(new_angle, goal_angle)) { + new_angle -= modifier_angle * time_difference; + if (new_angle < 0) { + new_angle += TAU; + } + if (!IsAngleGreater(new_angle, goal_angle)) { + return goal_angle; + } + } else if (IsAngleSmaller(new_angle, goal_angle)) { + new_angle += modifier_angle * time_difference; + if (new_angle >= TAU) { + new_angle -= TAU; + } + if (!IsAngleSmaller(new_angle, goal_angle)) { + return goal_angle; + } + } else { + return goal_angle; + } + return new_angle; + } + + void SetGoalAngle(bool r, bool l, bool u, bool d) { + // Move to the right + if (r && !u && !d) { + goal_angle = 0.0f; + } + + // Move to the upper right + if (r && u && !d) { + goal_angle = Common::PI * 0.25f; + } + + // Move up + if (u && !l && !r) { + goal_angle = Common::PI * 0.5f; + } + + // Move to the upper left + if (l && u && !d) { + goal_angle = Common::PI * 0.75f; + } + + // Move to the left + if (l && !u && !d) { + goal_angle = Common::PI; + } + + // Move to the bottom left + if (l && !u && d) { + goal_angle = Common::PI * 1.25f; + } + + // Move down + if (d && !l && !r) { + goal_angle = Common::PI * 1.5f; + } + + // Move to the bottom right + if (r && !u && d) { + goal_angle = Common::PI * 1.75f; + } + } + + void UpdateUpButtonStatus(const Common::Input::CallbackStatus& button_callback) { + up_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateDownButtonStatus(const Common::Input::CallbackStatus& button_callback) { + down_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateLeftButtonStatus(const Common::Input::CallbackStatus& button_callback) { + left_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateRightButtonStatus(const Common::Input::CallbackStatus& button_callback) { + right_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateModButtonStatus(const Common::Input::CallbackStatus& button_callback) { + modifier_status = button_callback.button_status.value; + UpdateStatus(); + } + + void UpdateStatus() { + const float coef = modifier_status ? modifier_scale : 1.0f; + + bool r = right_status; + bool l = left_status; + bool u = up_status; + bool d = down_status; + + // Eliminate contradictory movements + if (r && l) { + r = false; + l = false; + } + if (u && d) { + u = false; + d = false; + } + + // Move if a key is pressed + if (r || l || u || d) { + amplitude = coef; + } else { + amplitude = 0; + } + + const auto now = std::chrono::steady_clock::now(); + const auto time_difference = static_cast<u64>( + std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); + + if (time_difference < 10) { + // Disable analog mode if inputs are too fast + SetGoalAngle(r, l, u, d); + angle = goal_angle; + } else { + angle = GetAngle(now); + SetGoalAngle(r, l, u, d); + } + + last_update = now; + Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + last_x_axis_value = status.stick_status.x.raw_value; + last_y_axis_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + void ForceUpdate() override { + up->ForceUpdate(); + down->ForceUpdate(); + left->ForceUpdate(); + right->ForceUpdate(); + modifier->ForceUpdate(); + } + + void SoftUpdate() override { + Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + if (last_x_axis_value == status.stick_status.x.raw_value && + last_y_axis_value == status.stick_status.y.raw_value) { + return; + } + last_x_axis_value = status.stick_status.x.raw_value; + last_y_axis_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + Common::Input::StickStatus GetStatus() const { + Common::Input::StickStatus status{}; + status.x.properties = properties; + status.y.properties = properties; + if (Settings::values.emulate_analog_keyboard) { + const auto now = std::chrono::steady_clock::now(); + float angle_ = GetAngle(now); + status.x.raw_value = std::cos(angle_) * amplitude; + status.y.raw_value = std::sin(angle_) * amplitude; + return status; + } + constexpr float SQRT_HALF = 0.707106781f; + int x = 0, y = 0; + if (right_status) { + ++x; + } + if (left_status) { + --x; + } + if (up_status) { + ++y; + } + if (down_status) { + --y; + } + const float coef = modifier_status ? modifier_scale : 1.0f; + status.x.raw_value = static_cast<float>(x) * coef * (y == 0 ? 1.0f : SQRT_HALF); + status.y.raw_value = static_cast<float>(y) * coef * (x == 0 ? 1.0f : SQRT_HALF); + return status; + } + +private: + Button up; + Button down; + Button left; + Button right; + Button modifier; + float modifier_scale{}; + float modifier_angle{}; + float angle{}; + float goal_angle{}; + float amplitude{}; + bool up_status{}; + bool down_status{}; + bool left_status{}; + bool right_status{}; + bool modifier_status{}; + float last_x_axis_value{}; + float last_y_axis_value{}; + const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false}; + std::chrono::time_point<std::chrono::steady_clock> last_update; +}; + +std::unique_ptr<Common::Input::InputDevice> StickFromButton::Create( + const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto up = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>( + params.Get("up", null_engine)); + auto down = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>( + params.Get("down", null_engine)); + auto left = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>( + params.Get("left", null_engine)); + auto right = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>( + params.Get("right", null_engine)); + auto modifier = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>( + params.Get("modifier", null_engine)); + auto modifier_scale = params.Get("modifier_scale", 0.5f); + auto modifier_angle = params.Get("modifier_angle", 5.5f); + return std::make_unique<Stick>(std::move(up), std::move(down), std::move(left), + std::move(right), std::move(modifier), modifier_scale, + modifier_angle); +} + +} // namespace InputCommon diff --git a/src/input_common/analog_from_button.h b/src/input_common/helpers/stick_from_buttons.h index bbd583dd9..437ace4f7 100755..100644 --- a/src/input_common/analog_from_button.h +++ b/src/input_common/helpers/stick_from_buttons.h @@ -4,8 +4,7 @@ #pragma once -#include <memory> -#include "core/frontend/input.h" +#include "common/input.h" namespace InputCommon { @@ -13,7 +12,7 @@ namespace InputCommon { * An analog device factory that takes direction button devices and combines them into a analog * device. */ -class AnalogFromButton final : public Input::Factory<Input::AnalogDevice> { +class StickFromButton final : public Common::Input::Factory<Common::Input::InputDevice> { public: /** * Creates an analog device from direction button devices @@ -25,7 +24,7 @@ public: * - "modifier": a serialized ParamPackage for creating a button device as the modifier * - "modifier_scale": a float for the multiplier the modifier gives to the position */ - std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override; }; } // namespace InputCommon diff --git a/src/input_common/helpers/touch_from_buttons.cpp b/src/input_common/helpers/touch_from_buttons.cpp new file mode 100644 index 000000000..ece1e3b32 --- /dev/null +++ b/src/input_common/helpers/touch_from_buttons.cpp @@ -0,0 +1,84 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <algorithm> +#include "common/settings.h" +#include "core/frontend/framebuffer_layout.h" +#include "input_common/helpers/touch_from_buttons.h" + +namespace InputCommon { + +class TouchFromButtonDevice final : public Common::Input::InputDevice { +public: + using Button = std::unique_ptr<Common::Input::InputDevice>; + TouchFromButtonDevice(Button button_, int touch_id_, float x_, float y_) + : button(std::move(button_)), touch_id(touch_id_), x(x_), y(y_) { + last_button_value = false; + button->SetCallback({ + .on_change = + [this](const Common::Input::CallbackStatus& callback_) { + UpdateButtonStatus(callback_); + }, + }); + button->ForceUpdate(); + } + + void ForceUpdate() override { + button->ForceUpdate(); + } + + Common::Input::TouchStatus GetStatus(bool pressed) const { + const Common::Input::ButtonStatus button_status{ + .value = pressed, + }; + Common::Input::TouchStatus status{ + .pressed = button_status, + .x = {}, + .y = {}, + .id = touch_id, + }; + status.x.properties = properties; + status.y.properties = properties; + + if (!pressed) { + return status; + } + + status.x.raw_value = x; + status.y.raw_value = y; + return status; + } + + void UpdateButtonStatus(const Common::Input::CallbackStatus& button_callback) { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Touch, + .touch_status = GetStatus(button_callback.button_status.value), + }; + if (last_button_value != button_callback.button_status.value) { + last_button_value = button_callback.button_status.value; + TriggerOnChange(status); + } + } + +private: + Button button; + bool last_button_value; + const int touch_id; + const float x; + const float y; + const Common::Input::AnalogProperties properties{0.0f, 1.0f, 0.5f, 0.0f, false}; +}; + +std::unique_ptr<Common::Input::InputDevice> TouchFromButton::Create( + const Common::ParamPackage& params) { + const std::string null_engine = Common::ParamPackage{{"engine", "null"}}.Serialize(); + auto button = Common::Input::CreateDeviceFromString<Common::Input::InputDevice>( + params.Get("button", null_engine)); + const auto touch_id = params.Get("touch_id", 0); + const float x = params.Get("x", 0.0f) / 1280.0f; + const float y = params.Get("y", 0.0f) / 720.0f; + return std::make_unique<TouchFromButtonDevice>(std::move(button), touch_id, x, y); +} + +} // namespace InputCommon diff --git a/src/input_common/touch_from_button.h b/src/input_common/helpers/touch_from_buttons.h index 8b4d1aa96..628f18215 100644 --- a/src/input_common/touch_from_button.h +++ b/src/input_common/helpers/touch_from_buttons.h @@ -4,20 +4,19 @@ #pragma once -#include <memory> -#include "core/frontend/input.h" +#include "common/input.h" namespace InputCommon { /** * A touch device factory that takes a list of button devices and combines them into a touch device. */ -class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { +class TouchFromButton final : public Common::Input::Factory<Common::Input::InputDevice> { public: /** * Creates a touch device from a list of button devices */ - std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; + std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override; }; } // namespace InputCommon diff --git a/src/input_common/udp/protocol.cpp b/src/input_common/helpers/udp_protocol.cpp index 5e50bd612..cdeab7e11 100644 --- a/src/input_common/udp/protocol.cpp +++ b/src/input_common/helpers/udp_protocol.cpp @@ -5,7 +5,7 @@ #include <cstddef> #include <cstring> #include "common/logging/log.h" -#include "input_common/udp/protocol.h" +#include "input_common/helpers/udp_protocol.h" namespace InputCommon::CemuhookUDP { diff --git a/src/input_common/udp/protocol.h b/src/input_common/helpers/udp_protocol.h index 1bdc9209e..bcba12c58 100644 --- a/src/input_common/udp/protocol.h +++ b/src/input_common/helpers/udp_protocol.h @@ -56,6 +56,12 @@ constexpr Type GetMessageType(); namespace Request { +enum RegisterFlags : u8 { + AllPads, + PadID, + PadMACAdddress, +}; + struct Version {}; /** * Requests the server to send information about what controllers are plugged into the ports @@ -77,13 +83,8 @@ static_assert(std::is_trivially_copyable_v<PortInfo>, * timeout seems to be 5 seconds. */ struct PadData { - enum class Flags : u8 { - AllPorts, - Id, - Mac, - }; /// Determines which method will be used as a look up for the controller - Flags flags{}; + RegisterFlags flags{}; /// Index of the port of the controller to retrieve data about u8 port_id{}; /// Mac address of the controller to retrieve data about @@ -113,6 +114,36 @@ Message<T> Create(const T data, const u32 client_id = 0) { namespace Response { +enum class ConnectionType : u8 { + None, + Usb, + Bluetooth, +}; + +enum class State : u8 { + Disconnected, + Reserved, + Connected, +}; + +enum class Model : u8 { + None, + PartialGyro, + FullGyro, + Generic, +}; + +enum class Battery : u8 { + None = 0x00, + Dying = 0x01, + Low = 0x02, + Medium = 0x03, + High = 0x04, + Full = 0x05, + Charging = 0xEE, + Charged = 0xEF, +}; + struct Version { u16_le version{}; }; @@ -122,11 +153,11 @@ static_assert(std::is_trivially_copyable_v<Version>, struct PortInfo { u8 id{}; - u8 state{}; - u8 model{}; - u8 connection_type{}; + State state{}; + Model model{}; + ConnectionType connection_type{}; MacAddress mac; - u8 battery{}; + Battery battery{}; u8 is_pad_active{}; }; static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); @@ -177,18 +208,18 @@ struct PadData { u8 right_stick_y{}; struct AnalogButton { - u8 button_8{}; - u8 button_7{}; - u8 button_6{}; - u8 button_5{}; - u8 button_12{}; - u8 button_11{}; - u8 button_10{}; - u8 button_9{}; - u8 button_16{}; - u8 button_15{}; - u8 button_14{}; - u8 button_13{}; + u8 button_dpad_left_analog{}; + u8 button_dpad_down_analog{}; + u8 button_dpad_right_analog{}; + u8 button_dpad_up_analog{}; + u8 button_square_analog{}; + u8 button_cross_analog{}; + u8 button_circle_analog{}; + u8 button_triangle_analog{}; + u8 button_r1_analog{}; + u8 button_l1_analog{}; + u8 trigger_r2{}; + u8 trigger_l2{}; } analog_button; std::array<TouchPad, 2> touch; diff --git a/src/input_common/input_engine.cpp b/src/input_common/input_engine.cpp new file mode 100644 index 000000000..9c17ca4f7 --- /dev/null +++ b/src/input_common/input_engine.cpp @@ -0,0 +1,362 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "input_common/input_engine.h" + +namespace InputCommon { + +void InputEngine::PreSetController(const PadIdentifier& identifier) { + std::lock_guard lock{mutex}; + controller_list.try_emplace(identifier); +} + +void InputEngine::PreSetButton(const PadIdentifier& identifier, int button) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.buttons.try_emplace(button, false); +} + +void InputEngine::PreSetHatButton(const PadIdentifier& identifier, int button) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.hat_buttons.try_emplace(button, u8{0}); +} + +void InputEngine::PreSetAxis(const PadIdentifier& identifier, int axis) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.axes.try_emplace(axis, 0.0f); +} + +void InputEngine::PreSetMotion(const PadIdentifier& identifier, int motion) { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + controller.motions.try_emplace(motion); +} + +void InputEngine::SetButton(const PadIdentifier& identifier, int button, bool value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.buttons.insert_or_assign(button, value); + } + } + TriggerOnButtonChange(identifier, button, value); +} + +void InputEngine::SetHatButton(const PadIdentifier& identifier, int button, u8 value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.hat_buttons.insert_or_assign(button, value); + } + } + TriggerOnHatButtonChange(identifier, button, value); +} + +void InputEngine::SetAxis(const PadIdentifier& identifier, int axis, f32 value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.axes.insert_or_assign(axis, value); + } + } + TriggerOnAxisChange(identifier, axis, value); +} + +void InputEngine::SetBattery(const PadIdentifier& identifier, BatteryLevel value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.battery = value; + } + } + TriggerOnBatteryChange(identifier, value); +} + +void InputEngine::SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value) { + { + std::lock_guard lock{mutex}; + ControllerData& controller = controller_list.at(identifier); + if (!configuring) { + controller.motions.insert_or_assign(motion, value); + } + } + TriggerOnMotionChange(identifier, motion, value); +} + +bool InputEngine::GetButton(const PadIdentifier& identifier, int button) const { + std::lock_guard lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return false; + } + const ControllerData& controller = controller_iter->second; + const auto button_iter = controller.buttons.find(button); + if (button_iter == controller.buttons.cend()) { + LOG_ERROR(Input, "Invalid button {}", button); + return false; + } + return button_iter->second; +} + +bool InputEngine::GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const { + std::lock_guard lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return false; + } + const ControllerData& controller = controller_iter->second; + const auto hat_iter = controller.hat_buttons.find(button); + if (hat_iter == controller.hat_buttons.cend()) { + LOG_ERROR(Input, "Invalid hat button {}", button); + return false; + } + return (hat_iter->second & direction) != 0; +} + +f32 InputEngine::GetAxis(const PadIdentifier& identifier, int axis) const { + std::lock_guard lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return 0.0f; + } + const ControllerData& controller = controller_iter->second; + const auto axis_iter = controller.axes.find(axis); + if (axis_iter == controller.axes.cend()) { + LOG_ERROR(Input, "Invalid axis {}", axis); + return 0.0f; + } + return axis_iter->second; +} + +BatteryLevel InputEngine::GetBattery(const PadIdentifier& identifier) const { + std::lock_guard lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return BatteryLevel::Charging; + } + const ControllerData& controller = controller_iter->second; + return controller.battery; +} + +BasicMotion InputEngine::GetMotion(const PadIdentifier& identifier, int motion) const { + std::lock_guard lock{mutex}; + const auto controller_iter = controller_list.find(identifier); + if (controller_iter == controller_list.cend()) { + LOG_ERROR(Input, "Invalid identifier guid={}, pad={}, port={}", identifier.guid.Format(), + identifier.pad, identifier.port); + return {}; + } + const ControllerData& controller = controller_iter->second; + return controller.motions.at(motion); +} + +void InputEngine::ResetButtonState() { + for (const auto& controller : controller_list) { + for (const auto& button : controller.second.buttons) { + SetButton(controller.first, button.first, false); + } + for (const auto& button : controller.second.hat_buttons) { + SetHatButton(controller.first, button.first, false); + } + } +} + +void InputEngine::ResetAnalogState() { + for (const auto& controller : controller_list) { + for (const auto& axis : controller.second.axes) { + SetAxis(controller.first, axis.first, 0.0); + } + } +} + +void InputEngine::TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value) { + std::lock_guard lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Button, button)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + + PreSetButton(identifier, button); + if (value == GetButton(identifier, button)) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Button, + .index = button, + .button_value = value, + }); +} + +void InputEngine::TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value) { + std::lock_guard lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::HatButton, button)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + for (std::size_t index = 1; index < 0xff; index <<= 1) { + bool button_value = (value & index) != 0; + if (button_value == GetHatButton(identifier, button, static_cast<u8>(index))) { + continue; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::HatButton, + .index = button, + .hat_name = GetHatButtonName(static_cast<u8>(index)), + }); + } +} + +void InputEngine::TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value) { + std::lock_guard lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Analog, axis)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + if (std::abs(value - GetAxis(identifier, axis)) < 0.5f) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Analog, + .index = axis, + .axis_value = value, + }); +} + +void InputEngine::TriggerOnBatteryChange(const PadIdentifier& identifier, + [[maybe_unused]] BatteryLevel value) { + std::lock_guard lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Battery, 0)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } +} + +void InputEngine::TriggerOnMotionChange(const PadIdentifier& identifier, int motion, + const BasicMotion& value) { + std::lock_guard lock{mutex_callback}; + for (const auto& poller_pair : callback_list) { + const InputIdentifier& poller = poller_pair.second; + if (!IsInputIdentifierEqual(poller, identifier, EngineInputType::Motion, motion)) { + continue; + } + if (poller.callback.on_change) { + poller.callback.on_change(); + } + } + if (!configuring || !mapping_callback.on_data) { + return; + } + if (std::abs(value.gyro_x) < 0.6f && std::abs(value.gyro_y) < 0.6f && + std::abs(value.gyro_z) < 0.6f) { + return; + } + mapping_callback.on_data(MappingData{ + .engine = GetEngineName(), + .pad = identifier, + .type = EngineInputType::Motion, + .index = motion, + .motion_value = value, + }); +} + +bool InputEngine::IsInputIdentifierEqual(const InputIdentifier& input_identifier, + const PadIdentifier& identifier, EngineInputType type, + int index) const { + if (input_identifier.type != type) { + return false; + } + if (input_identifier.index != index) { + return false; + } + if (input_identifier.identifier != identifier) { + return false; + } + return true; +} + +void InputEngine::BeginConfiguration() { + configuring = true; +} + +void InputEngine::EndConfiguration() { + configuring = false; +} + +const std::string& InputEngine::GetEngineName() const { + return input_engine; +} + +int InputEngine::SetCallback(InputIdentifier input_identifier) { + std::lock_guard lock{mutex_callback}; + callback_list.insert_or_assign(last_callback_key, std::move(input_identifier)); + return last_callback_key++; +} + +void InputEngine::SetMappingCallback(MappingCallback callback) { + std::lock_guard lock{mutex_callback}; + mapping_callback = std::move(callback); +} + +void InputEngine::DeleteCallback(int key) { + std::lock_guard lock{mutex_callback}; + const auto& iterator = callback_list.find(key); + if (iterator == callback_list.end()) { + LOG_ERROR(Input, "Tried to delete non-existent callback {}", key); + return; + } + callback_list.erase(iterator); +} + +} // namespace InputCommon diff --git a/src/input_common/input_engine.h b/src/input_common/input_engine.h new file mode 100644 index 000000000..390581c94 --- /dev/null +++ b/src/input_common/input_engine.h @@ -0,0 +1,229 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include <functional> +#include <mutex> +#include <unordered_map> + +#include "common/common_types.h" +#include "common/input.h" +#include "common/param_package.h" +#include "common/uuid.h" +#include "input_common/main.h" + +// Pad Identifier of data source +struct PadIdentifier { + Common::UUID guid{}; + std::size_t port{}; + std::size_t pad{}; + + friend constexpr bool operator==(const PadIdentifier&, const PadIdentifier&) = default; +}; + +// Basic motion data containing data from the sensors and a timestamp in microseconds +struct BasicMotion { + float gyro_x{}; + float gyro_y{}; + float gyro_z{}; + float accel_x{}; + float accel_y{}; + float accel_z{}; + u64 delta_timestamp{}; +}; + +// Stages of a battery charge +enum class BatteryLevel { + Empty, + Critical, + Low, + Medium, + Full, + Charging, +}; + +// Types of input that are stored in the engine +enum class EngineInputType { + None, + Button, + HatButton, + Analog, + Motion, + Battery, +}; + +namespace std { +// Hash used to create lists from PadIdentifier data +template <> +struct hash<PadIdentifier> { + size_t operator()(const PadIdentifier& pad_id) const noexcept { + u64 hash_value = pad_id.guid.uuid[1] ^ pad_id.guid.uuid[0]; + hash_value ^= (static_cast<u64>(pad_id.port) << 32); + hash_value ^= static_cast<u64>(pad_id.pad); + return static_cast<size_t>(hash_value); + } +}; + +} // namespace std + +namespace InputCommon { + +// Data from the engine and device needed for creating a ParamPackage +struct MappingData { + std::string engine{}; + PadIdentifier pad{}; + EngineInputType type{}; + int index{}; + bool button_value{}; + std::string hat_name{}; + f32 axis_value{}; + BasicMotion motion_value{}; +}; + +// Triggered if data changed on the controller +struct UpdateCallback { + std::function<void()> on_change; +}; + +// Triggered if data changed on the controller and the engine is on configuring mode +struct MappingCallback { + std::function<void(MappingData)> on_data; +}; + +// Input Identifier of data source +struct InputIdentifier { + PadIdentifier identifier; + EngineInputType type; + int index; + UpdateCallback callback; +}; + +class InputEngine { +public: + explicit InputEngine(std::string input_engine_) : input_engine{std::move(input_engine_)} {} + + virtual ~InputEngine() = default; + + // Enable configuring mode for mapping + void BeginConfiguration(); + + // Disable configuring mode for mapping + void EndConfiguration(); + + // Sets a led pattern for a controller + virtual void SetLeds([[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::LedStatus& led_status) {} + + // Sets rumble to a controller + virtual Common::Input::VibrationError SetRumble( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::VibrationStatus& vibration) { + return Common::Input::VibrationError::NotSupported; + } + + // Sets polling mode to a controller + virtual Common::Input::PollingError SetPollingMode( + [[maybe_unused]] const PadIdentifier& identifier, + [[maybe_unused]] const Common::Input::PollingMode vibration) { + return Common::Input::PollingError::NotSupported; + } + + // Returns the engine name + [[nodiscard]] const std::string& GetEngineName() const; + + /// Used for automapping features + virtual std::vector<Common::ParamPackage> GetInputDevices() const { + return {}; + } + + /// Retrieves the button mappings for the given device + virtual ButtonMapping GetButtonMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + } + + /// Retrieves the analog mappings for the given device + virtual AnalogMapping GetAnalogMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + } + + /// Retrieves the motion mappings for the given device + virtual MotionMapping GetMotionMappingForDevice( + [[maybe_unused]] const Common::ParamPackage& params) { + return {}; + } + + /// Retrieves the name of the given input. + virtual Common::Input::ButtonNames GetUIName( + [[maybe_unused]] const Common::ParamPackage& params) const { + return Common::Input::ButtonNames::Engine; + } + + /// Retrieves the index number of the given hat button direction + virtual u8 GetHatButtonId([[maybe_unused]] const std::string& direction_name) const { + return 0; + } + + void PreSetController(const PadIdentifier& identifier); + void PreSetButton(const PadIdentifier& identifier, int button); + void PreSetHatButton(const PadIdentifier& identifier, int button); + void PreSetAxis(const PadIdentifier& identifier, int axis); + void PreSetMotion(const PadIdentifier& identifier, int motion); + void ResetButtonState(); + void ResetAnalogState(); + + bool GetButton(const PadIdentifier& identifier, int button) const; + bool GetHatButton(const PadIdentifier& identifier, int button, u8 direction) const; + f32 GetAxis(const PadIdentifier& identifier, int axis) const; + BatteryLevel GetBattery(const PadIdentifier& identifier) const; + BasicMotion GetMotion(const PadIdentifier& identifier, int motion) const; + + int SetCallback(InputIdentifier input_identifier); + void SetMappingCallback(MappingCallback callback); + void DeleteCallback(int key); + +protected: + void SetButton(const PadIdentifier& identifier, int button, bool value); + void SetHatButton(const PadIdentifier& identifier, int button, u8 value); + void SetAxis(const PadIdentifier& identifier, int axis, f32 value); + void SetBattery(const PadIdentifier& identifier, BatteryLevel value); + void SetMotion(const PadIdentifier& identifier, int motion, const BasicMotion& value); + + virtual std::string GetHatButtonName([[maybe_unused]] u8 direction_value) const { + return "Unknown"; + } + +private: + struct ControllerData { + std::unordered_map<int, bool> buttons; + std::unordered_map<int, u8> hat_buttons; + std::unordered_map<int, float> axes; + std::unordered_map<int, BasicMotion> motions; + BatteryLevel battery{}; + }; + + void TriggerOnButtonChange(const PadIdentifier& identifier, int button, bool value); + void TriggerOnHatButtonChange(const PadIdentifier& identifier, int button, u8 value); + void TriggerOnAxisChange(const PadIdentifier& identifier, int axis, f32 value); + void TriggerOnBatteryChange(const PadIdentifier& identifier, BatteryLevel value); + void TriggerOnMotionChange(const PadIdentifier& identifier, int motion, + const BasicMotion& value); + + bool IsInputIdentifierEqual(const InputIdentifier& input_identifier, + const PadIdentifier& identifier, EngineInputType type, + int index) const; + + mutable std::mutex mutex; + mutable std::mutex mutex_callback; + bool configuring{false}; + const std::string input_engine; + int last_callback_key = 0; + std::unordered_map<PadIdentifier, ControllerData> controller_list; + std::unordered_map<int, InputIdentifier> callback_list; + MappingCallback mapping_callback; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.cpp b/src/input_common/input_mapping.cpp new file mode 100644 index 000000000..6e0024b2d --- /dev/null +++ b/src/input_common/input_mapping.cpp @@ -0,0 +1,207 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/common_types.h" +#include "common/settings.h" +#include "input_common/input_engine.h" +#include "input_common/input_mapping.h" + +namespace InputCommon { + +MappingFactory::MappingFactory() {} + +void MappingFactory::BeginMapping(Polling::InputType type) { + is_enabled = true; + input_type = type; + input_queue.Clear(); + first_axis = -1; + second_axis = -1; +} + +[[nodiscard]] const Common::ParamPackage MappingFactory::GetNextInput() { + Common::ParamPackage input; + input_queue.Pop(input); + return input; +} + +void MappingFactory::RegisterInput(const MappingData& data) { + if (!is_enabled) { + return; + } + if (!IsDriverValid(data)) { + return; + } + + switch (input_type) { + case Polling::InputType::Button: + RegisterButton(data); + return; + case Polling::InputType::Stick: + RegisterStick(data); + return; + case Polling::InputType::Motion: + RegisterMotion(data); + return; + default: + return; + } +} + +void MappingFactory::StopMapping() { + is_enabled = false; + input_type = Polling::InputType::None; + input_queue.Clear(); +} + +void MappingFactory::RegisterButton(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid != Common::UUID{}) { + new_input.Set("guid", data.pad.guid.Format()); + } + new_input.Set("port", static_cast<int>(data.pad.port)); + new_input.Set("pad", static_cast<int>(data.pad.pad)); + + switch (data.type) { + case EngineInputType::Button: + // Workaround for old compatibility + if (data.engine == "keyboard") { + new_input.Set("code", data.index); + break; + } + new_input.Set("button", data.index); + break; + case EngineInputType::HatButton: + new_input.Set("hat", data.index); + new_input.Set("direction", data.hat_name); + break; + case EngineInputType::Analog: + // Ignore mouse axis when mapping buttons + if (data.engine == "mouse") { + return; + } + new_input.Set("axis", data.index); + new_input.Set("threshold", 0.5f); + break; + default: + return; + } + input_queue.Push(new_input); +} + +void MappingFactory::RegisterStick(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid != Common::UUID{}) { + new_input.Set("guid", data.pad.guid.Format()); + } + new_input.Set("port", static_cast<int>(data.pad.port)); + new_input.Set("pad", static_cast<int>(data.pad.pad)); + + // If engine is mouse map the mouse position as a joystick + if (data.engine == "mouse") { + new_input.Set("axis_x", 0); + new_input.Set("axis_y", 1); + new_input.Set("threshold", 0.5f); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.0f); + input_queue.Push(new_input); + return; + } + + switch (data.type) { + case EngineInputType::Button: + case EngineInputType::HatButton: + RegisterButton(data); + return; + case EngineInputType::Analog: + if (first_axis == data.index) { + return; + } + if (first_axis == -1) { + first_axis = data.index; + return; + } + new_input.Set("axis_x", first_axis); + new_input.Set("axis_y", data.index); + new_input.Set("threshold", 0.5f); + new_input.Set("range", 0.95f); + new_input.Set("deadzone", 0.15f); + break; + default: + return; + } + input_queue.Push(new_input); +} + +void MappingFactory::RegisterMotion(const MappingData& data) { + Common::ParamPackage new_input; + new_input.Set("engine", data.engine); + if (data.pad.guid != Common::UUID{}) { + new_input.Set("guid", data.pad.guid.Format()); + } + new_input.Set("port", static_cast<int>(data.pad.port)); + new_input.Set("pad", static_cast<int>(data.pad.pad)); + switch (data.type) { + case EngineInputType::Button: + case EngineInputType::HatButton: + RegisterButton(data); + return; + case EngineInputType::Analog: + if (first_axis == data.index) { + return; + } + if (second_axis == data.index) { + return; + } + if (first_axis == -1) { + first_axis = data.index; + return; + } + if (second_axis == -1) { + second_axis = data.index; + return; + } + new_input.Set("axis_x", first_axis); + new_input.Set("axis_y", second_axis); + new_input.Set("axis_z", data.index); + new_input.Set("range", 1.0f); + new_input.Set("deadzone", 0.20f); + break; + case EngineInputType::Motion: + new_input.Set("motion", data.index); + break; + default: + return; + } + input_queue.Push(new_input); +} + +bool MappingFactory::IsDriverValid(const MappingData& data) const { + // Only port 0 can be mapped on the keyboard + if (data.engine == "keyboard" && data.pad.port != 0) { + return false; + } + // To prevent mapping with two devices we disable any UDP except motion + if (!Settings::values.enable_udp_controller && data.engine == "cemuhookudp" && + data.type != EngineInputType::Motion) { + return false; + } + // The following drivers don't need to be mapped + if (data.engine == "tas") { + return false; + } + if (data.engine == "touch") { + return false; + } + if (data.engine == "touch_from_button") { + return false; + } + if (data.engine == "analog_from_button") { + return false; + } + return true; +} + +} // namespace InputCommon diff --git a/src/input_common/input_mapping.h b/src/input_common/input_mapping.h new file mode 100644 index 000000000..93564b5f8 --- /dev/null +++ b/src/input_common/input_mapping.h @@ -0,0 +1,83 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once +#include "common/threadsafe_queue.h" + +namespace InputCommon { +class InputEngine; +struct MappingData; + +class MappingFactory { +public: + MappingFactory(); + + /** + * Resets all variables to begin the mapping process + * @param type type of input desired to be returned + */ + void BeginMapping(Polling::InputType type); + + /// Returns an input event with mapping information from the input_queue + [[nodiscard]] const Common::ParamPackage GetNextInput(); + + /** + * Registers mapping input data from the driver + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterInput(const MappingData& data); + + /// Stop polling from all backends + void StopMapping(); + +private: + /** + * If provided data satisfies the requirements it will push an element to the input_queue + * Supported input: + * - Button: Creates a basic button ParamPackage + * - HatButton: Creates a basic hat button ParamPackage + * - Analog: Creates a basic analog ParamPackage + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterButton(const MappingData& data); + + /** + * If provided data satisfies the requirements it will push an element to the input_queue + * Supported input: + * - Button, HatButton: Pass the data to RegisterButton + * - Analog: Stores the first axis and on the second axis creates a basic stick ParamPackage + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterStick(const MappingData& data); + + /** + * If provided data satisfies the requirements it will push an element to the input_queue + * Supported input: + * - Button, HatButton: Pass the data to RegisterButton + * - Analog: Stores the first two axis and on the third axis creates a basic Motion + * ParamPackage + * - Motion: Creates a basic Motion ParamPackage + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + void RegisterMotion(const MappingData& data); + + /** + * Returns true if driver can be mapped + * @param data A struct containing all the information needed to create a proper + * ParamPackage + */ + bool IsDriverValid(const MappingData& data) const; + + Common::SPSCQueue<Common::ParamPackage> input_queue; + Polling::InputType input_type{Polling::InputType::None}; + bool is_enabled{}; + int first_axis = -1; + int second_axis = -1; +}; + +} // namespace InputCommon diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp new file mode 100644 index 000000000..7b370335f --- /dev/null +++ b/src/input_common/input_poller.cpp @@ -0,0 +1,970 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/common_types.h" +#include "common/input.h" + +#include "input_common/input_engine.h" +#include "input_common/input_poller.h" + +namespace InputCommon { + +class DummyInput final : public Common::Input::InputDevice { +public: + explicit DummyInput() = default; +}; + +class InputFromButton final : public Common::Input::InputDevice { +public: + explicit InputFromButton(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, + InputEngine* input_engine_) + : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + last_button_value = false; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromButton() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + if (status.button_status.value != last_button_value) { + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromHatButton final : public Common::Input::InputDevice { +public: + explicit InputFromHatButton(PadIdentifier identifier_, int button_, u8 direction_, bool toggle_, + bool inverted_, InputEngine* input_engine_) + : identifier(identifier_), button(button_), direction(direction_), toggle(toggle_), + inverted(inverted_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::HatButton, + .index = button, + .callback = engine_callback, + }; + last_button_value = false; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromHatButton() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::ButtonStatus GetStatus() const { + return { + .value = input_engine->GetHatButton(identifier, button, direction), + .inverted = inverted, + .toggle = toggle, + }; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Button, + .button_status = GetStatus(), + }; + + if (status.button_status.value != last_button_value) { + last_button_value = status.button_status.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const u8 direction; + const bool toggle; + const bool inverted; + int callback_key; + bool last_button_value; + InputEngine* input_engine; +}; + +class InputFromStick final : public Common::Input::InputDevice { +public: + explicit InputFromStick(PadIdentifier identifier_, int axis_x_, int axis_y_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + InputEngine* input_engine_) + : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), + properties_y(properties_y_), + input_engine(input_engine_), invert_axis_y{input_engine_->GetEngineName() == "sdl"} { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + } + + ~InputFromStick() override { + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + } + + Common::Input::StickStatus GetStatus() const { + Common::Input::StickStatus status; + status.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + // This is a workaround too keep compatibility with old yuzu versions. Vertical axis is + // inverted on SDL compared to Nintendo + if (invert_axis_y) { + status.y.raw_value = -status.y.raw_value; + } + return status; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + + last_axis_x_value = status.stick_status.x.raw_value; + last_axis_y_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Stick, + .stick_status = GetStatus(), + }; + + if (status.stick_status.x.raw_value != last_axis_x_value || + status.stick_status.y.raw_value != last_axis_y_value) { + last_axis_x_value = status.stick_status.x.raw_value; + last_axis_y_value = status.stick_status.y.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis_x; + const int axis_y; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + int callback_key_x; + int callback_key_y; + float last_axis_x_value; + float last_axis_y_value; + InputEngine* input_engine; + const bool invert_axis_y; +}; + +class InputFromTouch final : public Common::Input::InputDevice { +public: + explicit InputFromTouch(PadIdentifier identifier_, int touch_id_, int button_, bool toggle_, + bool inverted_, int axis_x_, int axis_y_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + InputEngine* input_engine_) + : identifier(identifier_), touch_id(touch_id_), button(button_), toggle(toggle_), + inverted(inverted_), axis_x(axis_x_), axis_y(axis_y_), properties_x(properties_x_), + properties_y(properties_y_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier button_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + last_button_value = false; + callback_key_button = input_engine->SetCallback(button_input_identifier); + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + } + + ~InputFromTouch() override { + input_engine->DeleteCallback(callback_key_button); + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + } + + Common::Input::TouchStatus GetStatus() const { + Common::Input::TouchStatus status; + status.id = touch_id; + status.pressed = { + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + status.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + return status; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Touch, + .touch_status = GetStatus(), + }; + + if (status.touch_status.x.raw_value != last_axis_x_value || + status.touch_status.y.raw_value != last_axis_y_value || + status.touch_status.pressed.value != last_button_value) { + last_axis_x_value = status.touch_status.x.raw_value; + last_axis_y_value = status.touch_status.y.raw_value; + last_button_value = status.touch_status.pressed.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int touch_id; + const int button; + const bool toggle; + const bool inverted; + const int axis_x; + const int axis_y; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + int callback_key_button; + int callback_key_x; + int callback_key_y; + bool last_button_value; + float last_axis_x_value; + float last_axis_y_value; + InputEngine* input_engine; +}; + +class InputFromTrigger final : public Common::Input::InputDevice { +public: + explicit InputFromTrigger(PadIdentifier identifier_, int button_, bool toggle_, bool inverted_, + int axis_, Common::Input::AnalogProperties properties_, + InputEngine* input_engine_) + : identifier(identifier_), button(button_), toggle(toggle_), inverted(inverted_), + axis(axis_), properties(properties_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier button_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Button, + .index = button, + .callback = engine_callback, + }; + const InputIdentifier axis_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis, + .callback = engine_callback, + }; + last_axis_value = 0.0f; + last_button_value = false; + callback_key_button = input_engine->SetCallback(button_input_identifier); + axis_callback_key = input_engine->SetCallback(axis_input_identifier); + } + + ~InputFromTrigger() override { + input_engine->DeleteCallback(callback_key_button); + input_engine->DeleteCallback(axis_callback_key); + } + + Common::Input::TriggerStatus GetStatus() const { + const Common::Input::AnalogStatus analog_status{ + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + const Common::Input::ButtonStatus button_status{ + .value = input_engine->GetButton(identifier, button), + .inverted = inverted, + .toggle = toggle, + }; + return { + .analog = analog_status, + .pressed = button_status, + }; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Trigger, + .trigger_status = GetStatus(), + }; + + if (status.trigger_status.analog.raw_value != last_axis_value || + status.trigger_status.pressed.value != last_button_value) { + last_axis_value = status.trigger_status.analog.raw_value; + last_button_value = status.trigger_status.pressed.value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int button; + const bool toggle; + const bool inverted; + const int axis; + const Common::Input::AnalogProperties properties; + int callback_key_button; + int axis_callback_key; + bool last_button_value; + float last_axis_value; + InputEngine* input_engine; +}; + +class InputFromAnalog final : public Common::Input::InputDevice { +public: + explicit InputFromAnalog(PadIdentifier identifier_, int axis_, + Common::Input::AnalogProperties properties_, + InputEngine* input_engine_) + : identifier(identifier_), axis(axis_), properties(properties_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis, + .callback = engine_callback, + }; + last_axis_value = 0.0f; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromAnalog() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::AnalogStatus GetStatus() const { + return { + .raw_value = input_engine->GetAxis(identifier, axis), + .properties = properties, + }; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Analog, + .analog_status = GetStatus(), + }; + + if (status.analog_status.raw_value != last_axis_value) { + last_axis_value = status.analog_status.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis; + const Common::Input::AnalogProperties properties; + int callback_key; + float last_axis_value; + InputEngine* input_engine; +}; + +class InputFromBattery final : public Common::Input::InputDevice { +public: + explicit InputFromBattery(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Battery, + .index = 0, + .callback = engine_callback, + }; + last_battery_value = Common::Input::BatteryStatus::Charging; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromBattery() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::BatteryStatus GetStatus() const { + return static_cast<Common::Input::BatteryLevel>(input_engine->GetBattery(identifier)); + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Battery, + .battery_status = GetStatus(), + }; + + last_battery_value = status.battery_status; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Battery, + .battery_status = GetStatus(), + }; + + if (status.battery_status != last_battery_value) { + last_battery_value = status.battery_status; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + int callback_key; + Common::Input::BatteryStatus last_battery_value; + InputEngine* input_engine; +}; + +class InputFromMotion final : public Common::Input::InputDevice { +public: + explicit InputFromMotion(PadIdentifier identifier_, int motion_sensor_, + InputEngine* input_engine_) + : identifier(identifier_), motion_sensor(motion_sensor_), input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier input_identifier{ + .identifier = identifier, + .type = EngineInputType::Motion, + .index = motion_sensor, + .callback = engine_callback, + }; + callback_key = input_engine->SetCallback(input_identifier); + } + + ~InputFromMotion() override { + input_engine->DeleteCallback(callback_key); + } + + Common::Input::MotionStatus GetStatus() const { + const auto basic_motion = input_engine->GetMotion(identifier, motion_sensor); + Common::Input::MotionStatus status{}; + const Common::Input::AnalogProperties properties = { + .deadzone = 0.001f, + .range = 1.0f, + .offset = 0.0f, + }; + status.accel.x = {.raw_value = basic_motion.accel_x, .properties = properties}; + status.accel.y = {.raw_value = basic_motion.accel_y, .properties = properties}; + status.accel.z = {.raw_value = basic_motion.accel_z, .properties = properties}; + status.gyro.x = {.raw_value = basic_motion.gyro_x, .properties = properties}; + status.gyro.y = {.raw_value = basic_motion.gyro_y, .properties = properties}; + status.gyro.z = {.raw_value = basic_motion.gyro_z, .properties = properties}; + status.delta_timestamp = basic_motion.delta_timestamp; + return status; + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + TriggerOnChange(status); + } + +private: + const PadIdentifier identifier; + const int motion_sensor; + int callback_key; + InputEngine* input_engine; +}; + +class InputFromAxisMotion final : public Common::Input::InputDevice { +public: + explicit InputFromAxisMotion(PadIdentifier identifier_, int axis_x_, int axis_y_, int axis_z_, + Common::Input::AnalogProperties properties_x_, + Common::Input::AnalogProperties properties_y_, + Common::Input::AnalogProperties properties_z_, + InputEngine* input_engine_) + : identifier(identifier_), axis_x(axis_x_), axis_y(axis_y_), axis_z(axis_z_), + properties_x(properties_x_), properties_y(properties_y_), properties_z(properties_z_), + input_engine(input_engine_) { + UpdateCallback engine_callback{[this]() { OnChange(); }}; + const InputIdentifier x_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_x, + .callback = engine_callback, + }; + const InputIdentifier y_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_y, + .callback = engine_callback, + }; + const InputIdentifier z_input_identifier{ + .identifier = identifier, + .type = EngineInputType::Analog, + .index = axis_z, + .callback = engine_callback, + }; + last_axis_x_value = 0.0f; + last_axis_y_value = 0.0f; + last_axis_z_value = 0.0f; + callback_key_x = input_engine->SetCallback(x_input_identifier); + callback_key_y = input_engine->SetCallback(y_input_identifier); + callback_key_z = input_engine->SetCallback(z_input_identifier); + } + + ~InputFromAxisMotion() override { + input_engine->DeleteCallback(callback_key_x); + input_engine->DeleteCallback(callback_key_y); + input_engine->DeleteCallback(callback_key_z); + } + + Common::Input::MotionStatus GetStatus() const { + Common::Input::MotionStatus status{}; + status.gyro.x = { + .raw_value = input_engine->GetAxis(identifier, axis_x), + .properties = properties_x, + }; + status.gyro.y = { + .raw_value = input_engine->GetAxis(identifier, axis_y), + .properties = properties_y, + }; + status.gyro.z = { + .raw_value = input_engine->GetAxis(identifier, axis_z), + .properties = properties_z, + }; + status.delta_timestamp = 5000; + status.force_update = true; + return status; + } + + void ForceUpdate() override { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + last_axis_x_value = status.motion_status.gyro.x.raw_value; + last_axis_y_value = status.motion_status.gyro.y.raw_value; + last_axis_z_value = status.motion_status.gyro.z.raw_value; + TriggerOnChange(status); + } + + void OnChange() { + const Common::Input::CallbackStatus status{ + .type = Common::Input::InputType::Motion, + .motion_status = GetStatus(), + }; + + if (status.motion_status.gyro.x.raw_value != last_axis_x_value || + status.motion_status.gyro.y.raw_value != last_axis_y_value || + status.motion_status.gyro.z.raw_value != last_axis_z_value) { + last_axis_x_value = status.motion_status.gyro.x.raw_value; + last_axis_y_value = status.motion_status.gyro.y.raw_value; + last_axis_z_value = status.motion_status.gyro.z.raw_value; + TriggerOnChange(status); + } + } + +private: + const PadIdentifier identifier; + const int axis_x; + const int axis_y; + const int axis_z; + const Common::Input::AnalogProperties properties_x; + const Common::Input::AnalogProperties properties_y; + const Common::Input::AnalogProperties properties_z; + int callback_key_x; + int callback_key_y; + int callback_key_z; + float last_axis_x_value; + float last_axis_y_value; + float last_axis_z_value; + InputEngine* input_engine; +}; + +class OutputFromIdentifier final : public Common::Input::OutputDevice { +public: + explicit OutputFromIdentifier(PadIdentifier identifier_, InputEngine* input_engine_) + : identifier(identifier_), input_engine(input_engine_) {} + + void SetLED(const Common::Input::LedStatus& led_status) override { + input_engine->SetLeds(identifier, led_status); + } + + Common::Input::VibrationError SetVibration( + const Common::Input::VibrationStatus& vibration_status) override { + return input_engine->SetRumble(identifier, vibration_status); + } + + Common::Input::PollingError SetPollingMode(Common::Input::PollingMode polling_mode) override { + return input_engine->SetPollingMode(identifier, polling_mode); + } + +private: + const PadIdentifier identifier; + InputEngine* input_engine; +}; + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateButtonDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + const auto button_id = params.Get("button", 0); + const auto keyboard_key = params.Get("code", 0); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + input_engine->PreSetController(identifier); + input_engine->PreSetButton(identifier, button_id); + input_engine->PreSetButton(identifier, keyboard_key); + if (keyboard_key != 0) { + return std::make_unique<InputFromButton>(identifier, keyboard_key, toggle, inverted, + input_engine.get()); + } + return std::make_unique<InputFromButton>(identifier, button_id, toggle, inverted, + input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateHatButtonDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + const auto button_id = params.Get("hat", 0); + const auto direction = input_engine->GetHatButtonId(params.Get("direction", "")); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + input_engine->PreSetController(identifier); + input_engine->PreSetHatButton(identifier, button_id); + return std::make_unique<InputFromHatButton>(identifier, button_id, direction, toggle, inverted, + input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateStickDevice( + const Common::ParamPackage& params) { + const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", "+") != "+", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + return std::make_unique<InputFromStick>(identifier, axis_x, axis_y, properties_x, properties_y, + input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + const auto axis = params.Get("axis", 0); + const Common::Input::AnalogProperties properties = { + .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f), + .range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f), + .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), + .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert", "+") == "-", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis); + return std::make_unique<InputFromAnalog>(identifier, axis, properties, input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTriggerDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + const auto button = params.Get("button", 0); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + const auto axis = params.Get("axis", 0); + const Common::Input::AnalogProperties properties = { + .deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f), + .range = std::clamp(params.Get("range", 1.0f), 0.25f, 2.50f), + .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), + .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert", false) != 0, + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis); + input_engine->PreSetButton(identifier, button); + return std::make_unique<InputFromTrigger>(identifier, button, toggle, inverted, axis, + properties, input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateTouchDevice( + const Common::ParamPackage& params) { + const auto touch_id = params.Get("touch_id", 0); + const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + const auto button = params.Get("button", 0); + const auto toggle = params.Get("toggle", false); + const auto inverted = params.Get("inverted", false); + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", false) != 0, + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + input_engine->PreSetButton(identifier, button); + return std::make_unique<InputFromTouch>(identifier, touch_id, button, toggle, inverted, axis_x, + axis_y, properties_x, properties_y, input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateBatteryDevice( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique<InputFromBattery>(identifier, input_engine.get()); +} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateMotionDevice( + Common::ParamPackage params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + if (params.Has("motion")) { + const auto motion_sensor = params.Get("motion", 0); + input_engine->PreSetController(identifier); + input_engine->PreSetMotion(identifier, motion_sensor); + return std::make_unique<InputFromMotion>(identifier, motion_sensor, input_engine.get()); + } + + const auto deadzone = std::clamp(params.Get("deadzone", 0.15f), 0.0f, 1.0f); + const auto range = std::clamp(params.Get("range", 1.0f), 0.25f, 1.50f); + const auto threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f); + + const auto axis_x = params.Get("axis_x", 0); + const Common::Input::AnalogProperties properties_x = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_x", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_x", "+") == "-", + }; + + const auto axis_y = params.Get("axis_y", 1); + const Common::Input::AnalogProperties properties_y = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_y", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_y", "+") != "+", + }; + + const auto axis_z = params.Get("axis_z", 1); + const Common::Input::AnalogProperties properties_z = { + .deadzone = deadzone, + .range = range, + .threshold = threshold, + .offset = std::clamp(params.Get("offset_z", 0.0f), -1.0f, 1.0f), + .inverted = params.Get("invert_z", "+") != "+", + }; + input_engine->PreSetController(identifier); + input_engine->PreSetAxis(identifier, axis_x); + input_engine->PreSetAxis(identifier, axis_y); + input_engine->PreSetAxis(identifier, axis_z); + return std::make_unique<InputFromAxisMotion>(identifier, axis_x, axis_y, axis_z, properties_x, + properties_y, properties_z, input_engine.get()); +} + +InputFactory::InputFactory(std::shared_ptr<InputEngine> input_engine_) + : input_engine(std::move(input_engine_)) {} + +std::unique_ptr<Common::Input::InputDevice> InputFactory::Create( + const Common::ParamPackage& params) { + if (params.Has("battery")) { + return CreateBatteryDevice(params); + } + if (params.Has("button") && params.Has("axis")) { + return CreateTriggerDevice(params); + } + if (params.Has("button") && params.Has("axis_x") && params.Has("axis_y")) { + return CreateTouchDevice(params); + } + if (params.Has("button") || params.Has("code")) { + return CreateButtonDevice(params); + } + if (params.Has("hat")) { + return CreateHatButtonDevice(params); + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return CreateMotionDevice(params); + } + if (params.Has("motion")) { + return CreateMotionDevice(params); + } + if (params.Has("axis_x") && params.Has("axis_y")) { + return CreateStickDevice(params); + } + if (params.Has("axis")) { + return CreateAnalogDevice(params); + } + LOG_ERROR(Input, "Invalid parameters given"); + return std::make_unique<DummyInput>(); +} + +OutputFactory::OutputFactory(std::shared_ptr<InputEngine> input_engine_) + : input_engine(std::move(input_engine_)) {} + +std::unique_ptr<Common::Input::OutputDevice> OutputFactory::Create( + const Common::ParamPackage& params) { + const PadIdentifier identifier = { + .guid = Common::UUID{params.Get("guid", "")}, + .port = static_cast<std::size_t>(params.Get("port", 0)), + .pad = static_cast<std::size_t>(params.Get("pad", 0)), + }; + + input_engine->PreSetController(identifier); + return std::make_unique<OutputFromIdentifier>(identifier, input_engine.get()); +} + +} // namespace InputCommon diff --git a/src/input_common/input_poller.h b/src/input_common/input_poller.h new file mode 100644 index 000000000..8a0977d58 --- /dev/null +++ b/src/input_common/input_poller.h @@ -0,0 +1,217 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +namespace Input { +class InputDevice; + +template <typename InputDevice> +class Factory; +}; // namespace Input + +namespace InputCommon { +class InputEngine; + +class OutputFactory final : public Common::Input::Factory<Common::Input::OutputDevice> { +public: + explicit OutputFactory(std::shared_ptr<InputEngine> input_engine_); + + /** + * Creates an output device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid" text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique output device with the parameters specified + */ + std::unique_ptr<Common::Input::OutputDevice> Create( + const Common::ParamPackage& params) override; + +private: + std::shared_ptr<InputEngine> input_engine; +}; + +/** + * An Input factory. It receives input events and forward them to all input devices it created. + */ +class InputFactory final : public Common::Input::Factory<Common::Input::InputDevice> { +public: + explicit InputFactory(std::shared_ptr<InputEngine> input_engine_); + + /** + * Creates an input device from the parameters given. Identifies the type of input to be + * returned if it contains the following parameters: + * - button: Contains "button" or "code" + * - hat_button: Contains "hat" + * - analog: Contains "axis" + * - trigger: Contains "button" and "axis" + * - stick: Contains "axis_x" and "axis_y" + * - motion: Contains "axis_x", "axis_y" and "axis_z" + * - motion: Contains "motion" + * - touch: Contains "button", "axis_x" and "axis_y" + * - battery: Contains "battery" + * - output: Contains "output" + * @param params contains parameters for creating the device: + * - "code": the code of the keyboard key to bind with the input + * - "button": same as "code" but for controller buttons + * - "hat": similar as "button" but it's a group of hat buttons from SDL + * - "axis": the axis number of the axis to bind with the input + * - "motion": the motion number of the motion to bind with the input + * - "axis_x": same as axis but specifying horizontal direction + * - "axis_y": same as axis but specifying vertical direction + * - "axis_z": same as axis but specifying forward direction + * - "battery": Only used as a placeholder to set the input type + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> Create(const Common::ParamPackage& params) override; + +private: + /** + * Creates a button device from the parameters given. + * @param params contains parameters for creating the device: + * - "code": the code of the keyboard key to bind with the input + * - "button": same as "code" but for controller buttons + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateButtonDevice( + const Common::ParamPackage& params); + + /** + * Creates a hat button device from the parameters given. + * @param params contains parameters for creating the device: + * - "button": the controller hat id to bind with the input + * - "direction": the direction id to be detected + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateHatButtonDevice( + const Common::ParamPackage& params); + + /** + * Creates a stick device from the parameters given. + * @param params contains parameters for creating the device: + * - "axis_x": the controller horizontal axis id to bind with the input + * - "axis_y": the controller vertical axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset_x": the amount of offset in the x axis + * - "offset_y": the amount of offset in the y axis + * - "invert_x": inverts the sign of the horizontal axis + * - "invert_y": inverts the sign of the vertical axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateStickDevice( + const Common::ParamPackage& params); + + /** + * Creates an analog device from the parameters given. + * @param params contains parameters for creating the device: + * - "axis": the controller axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset": the amount of offset in the axis + * - "invert": inverts the sign of the axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateAnalogDevice( + const Common::ParamPackage& params); + + /** + * Creates a trigger device from the parameters given. + * @param params contains parameters for creating the device: + * - "button": the controller hat id to bind with the input + * - "direction": the direction id to be detected + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "axis": the controller axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset": the amount of offset in the axis + * - "invert": inverts the sign of the axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateTriggerDevice( + const Common::ParamPackage& params); + + /** + * Creates a touch device from the parameters given. + * @param params contains parameters for creating the device: + * - "button": the controller hat id to bind with the input + * - "direction": the direction id to be detected + * - "toggle": press once to enable, press again to disable + * - "inverted": inverts the output of the button + * - "axis_x": the controller horizontal axis id to bind with the input + * - "axis_y": the controller vertical axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "threshold": the minimum required value to considered pressed + * - "offset_x": the amount of offset in the x axis + * - "offset_y": the amount of offset in the y axis + * - "invert_x": inverts the sign of the horizontal axis + * - "invert_y": inverts the sign of the vertical axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateTouchDevice( + const Common::ParamPackage& params); + + /** + * Creates a battery device from the parameters given. + * @param params contains parameters for creating the device: + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateBatteryDevice( + const Common::ParamPackage& params); + + /** + * Creates a motion device from the parameters given. + * @param params contains parameters for creating the device: + * - "axis_x": the controller horizontal axis id to bind with the input + * - "axis_y": the controller vertical axis id to bind with the input + * - "axis_z": the controller forward axis id to bind with the input + * - "deadzone": the minimum required value to be detected + * - "range": the maximum value required to reach 100% + * - "offset_x": the amount of offset in the x axis + * - "offset_y": the amount of offset in the y axis + * - "offset_z": the amount of offset in the z axis + * - "invert_x": inverts the sign of the horizontal axis + * - "invert_y": inverts the sign of the vertical axis + * - "invert_z": inverts the sign of the forward axis + * - "guid": text string for identifying controllers + * - "port": port of the connected device + * - "pad": slot of the connected controller + * @returns a unique input device with the parameters specified + */ + std::unique_ptr<Common::Input::InputDevice> CreateMotionDevice(Common::ParamPackage params); + + std::shared_ptr<InputEngine> input_engine; +}; +} // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp deleted file mode 100644 index 8261e76fd..000000000 --- a/src/input_common/keyboard.cpp +++ /dev/null @@ -1,121 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <atomic> -#include <list> -#include <mutex> -#include <utility> -#include "input_common/keyboard.h" - -namespace InputCommon { - -class KeyButton final : public Input::ButtonDevice { -public: - explicit KeyButton(std::shared_ptr<KeyButtonList> key_button_list_, bool toggle_) - : key_button_list(std::move(key_button_list_)), toggle(toggle_) {} - - ~KeyButton() override; - - bool GetStatus() const override { - if (toggle) { - return toggled_status.load(std::memory_order_relaxed); - } - return status.load(); - } - - void ToggleButton() { - if (lock) { - return; - } - lock = true; - const bool old_toggle_status = toggled_status.load(); - toggled_status.store(!old_toggle_status); - } - - void UnlockButton() { - lock = false; - } - - friend class KeyButtonList; - -private: - std::shared_ptr<KeyButtonList> key_button_list; - std::atomic<bool> status{false}; - std::atomic<bool> toggled_status{false}; - bool lock{false}; - const bool toggle; -}; - -struct KeyButtonPair { - int key_code; - KeyButton* key_button; -}; - -class KeyButtonList { -public: - void AddKeyButton(int key_code, KeyButton* key_button) { - std::lock_guard guard{mutex}; - list.push_back(KeyButtonPair{key_code, key_button}); - } - - void RemoveKeyButton(const KeyButton* key_button) { - std::lock_guard guard{mutex}; - list.remove_if( - [key_button](const KeyButtonPair& pair) { return pair.key_button == key_button; }); - } - - void ChangeKeyStatus(int key_code, bool pressed) { - std::lock_guard guard{mutex}; - for (const KeyButtonPair& pair : list) { - if (pair.key_code == key_code) { - pair.key_button->status.store(pressed); - if (pressed) { - pair.key_button->ToggleButton(); - } else { - pair.key_button->UnlockButton(); - } - pair.key_button->TriggerOnChange(); - } - } - } - - void ChangeAllKeyStatus(bool pressed) { - std::lock_guard guard{mutex}; - for (const KeyButtonPair& pair : list) { - pair.key_button->status.store(pressed); - } - } - -private: - std::mutex mutex; - std::list<KeyButtonPair> list; -}; - -Keyboard::Keyboard() : key_button_list{std::make_shared<KeyButtonList>()} {} - -KeyButton::~KeyButton() { - key_button_list->RemoveKeyButton(this); -} - -std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage& params) { - const int key_code = params.Get("code", 0); - const bool toggle = params.Get("toggle", false); - std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list, toggle); - key_button_list->AddKeyButton(key_code, button.get()); - return button; -} - -void Keyboard::PressKey(int key_code) { - key_button_list->ChangeKeyStatus(key_code, true); -} - -void Keyboard::ReleaseKey(int key_code) { - key_button_list->ChangeKeyStatus(key_code, false); -} - -void Keyboard::ReleaseAllKeys() { - key_button_list->ChangeAllKeyStatus(false); -} - -} // namespace InputCommon diff --git a/src/input_common/keyboard.h b/src/input_common/keyboard.h deleted file mode 100644 index 861950472..000000000 --- a/src/input_common/keyboard.h +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2017 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "core/frontend/input.h" - -namespace InputCommon { - -class KeyButtonList; - -/** - * A button device factory representing a keyboard. It receives keyboard events and forward them - * to all button devices it created. - */ -class Keyboard final : public Input::Factory<Input::ButtonDevice> { -public: - Keyboard(); - - /** - * Creates a button device from a keyboard key - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; - - /** - * Sets the status of all buttons bound with the key to pressed - * @param key_code the code of the key to press - */ - void PressKey(int key_code); - - /** - * Sets the status of all buttons bound with the key to released - * @param key_code the code of the key to release - */ - void ReleaseKey(int key_code); - - void ReleaseAllKeys(); - -private: - std::shared_ptr<KeyButtonList> key_button_list; -}; - -} // namespace InputCommon diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index f3907c65a..940744c5f 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -4,146 +4,173 @@ #include <memory> #include <thread> +#include "common/input.h" #include "common/param_package.h" -#include "common/settings.h" -#include "input_common/analog_from_button.h" -#include "input_common/gcadapter/gc_adapter.h" -#include "input_common/gcadapter/gc_poller.h" -#include "input_common/keyboard.h" +#include "input_common/drivers/gc_adapter.h" +#include "input_common/drivers/keyboard.h" +#include "input_common/drivers/mouse.h" +#include "input_common/drivers/tas_input.h" +#include "input_common/drivers/touch_screen.h" +#include "input_common/drivers/udp_client.h" +#include "input_common/helpers/stick_from_buttons.h" +#include "input_common/helpers/touch_from_buttons.h" +#include "input_common/input_engine.h" +#include "input_common/input_mapping.h" +#include "input_common/input_poller.h" #include "input_common/main.h" -#include "input_common/motion_from_button.h" -#include "input_common/mouse/mouse_input.h" -#include "input_common/mouse/mouse_poller.h" -#include "input_common/tas/tas_input.h" -#include "input_common/tas/tas_poller.h" -#include "input_common/touch_from_button.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" #ifdef HAVE_SDL2 -#include "input_common/sdl/sdl.h" +#include "input_common/drivers/sdl_driver.h" #endif namespace InputCommon { struct InputSubsystem::Impl { void Initialize() { - gcadapter = std::make_shared<GCAdapter::Adapter>(); - gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); - Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); - gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); - Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); - gcvibration = std::make_shared<GCVibrationFactory>(gcadapter); - Input::RegisterFactory<Input::VibrationDevice>("gcpad", gcvibration); - - keyboard = std::make_shared<Keyboard>(); - Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); - Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", - std::make_shared<AnalogFromButton>()); - Input::RegisterFactory<Input::MotionDevice>("keyboard", - std::make_shared<MotionFromButton>()); - Input::RegisterFactory<Input::TouchDevice>("touch_from_button", - std::make_shared<TouchFromButtonFactory>()); + mapping_factory = std::make_shared<MappingFactory>(); + MappingCallback mapping_callback{[this](MappingData data) { RegisterInput(data); }}; + + keyboard = std::make_shared<Keyboard>("keyboard"); + keyboard->SetMappingCallback(mapping_callback); + keyboard_factory = std::make_shared<InputFactory>(keyboard); + keyboard_output_factory = std::make_shared<OutputFactory>(keyboard); + Common::Input::RegisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName(), + keyboard_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName(), + keyboard_output_factory); + + mouse = std::make_shared<Mouse>("mouse"); + mouse->SetMappingCallback(mapping_callback); + mouse_factory = std::make_shared<InputFactory>(mouse); + mouse_output_factory = std::make_shared<OutputFactory>(mouse); + Common::Input::RegisterFactory<Common::Input::InputDevice>(mouse->GetEngineName(), + mouse_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName(), + mouse_output_factory); + + touch_screen = std::make_shared<TouchScreen>("touch"); + touch_screen_factory = std::make_shared<InputFactory>(touch_screen); + Common::Input::RegisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName(), + touch_screen_factory); + + gcadapter = std::make_shared<GCAdapter>("gcpad"); + gcadapter->SetMappingCallback(mapping_callback); + gcadapter_input_factory = std::make_shared<InputFactory>(gcadapter); + gcadapter_output_factory = std::make_shared<OutputFactory>(gcadapter); + Common::Input::RegisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName(), + gcadapter_input_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName(), + gcadapter_output_factory); + + udp_client = std::make_shared<CemuhookUDP::UDPClient>("cemuhookudp"); + udp_client->SetMappingCallback(mapping_callback); + udp_client_input_factory = std::make_shared<InputFactory>(udp_client); + udp_client_output_factory = std::make_shared<OutputFactory>(udp_client); + Common::Input::RegisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName(), + udp_client_input_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName(), + udp_client_output_factory); + + tas_input = std::make_shared<TasInput::Tas>("tas"); + tas_input->SetMappingCallback(mapping_callback); + tas_input_factory = std::make_shared<InputFactory>(tas_input); + tas_output_factory = std::make_shared<OutputFactory>(tas_input); + Common::Input::RegisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName(), + tas_input_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName(), + tas_output_factory); #ifdef HAVE_SDL2 - sdl = SDL::Init(); + sdl = std::make_shared<SDLDriver>("sdl"); + sdl->SetMappingCallback(mapping_callback); + sdl_input_factory = std::make_shared<InputFactory>(sdl); + sdl_output_factory = std::make_shared<OutputFactory>(sdl); + Common::Input::RegisterFactory<Common::Input::InputDevice>(sdl->GetEngineName(), + sdl_input_factory); + Common::Input::RegisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName(), + sdl_output_factory); #endif - udp = std::make_shared<InputCommon::CemuhookUDP::Client>(); - udpmotion = std::make_shared<UDPMotionFactory>(udp); - Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion); - udptouch = std::make_shared<UDPTouchFactory>(udp); - Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch); - - mouse = std::make_shared<MouseInput::Mouse>(); - mousebuttons = std::make_shared<MouseButtonFactory>(mouse); - Input::RegisterFactory<Input::ButtonDevice>("mouse", mousebuttons); - mouseanalog = std::make_shared<MouseAnalogFactory>(mouse); - Input::RegisterFactory<Input::AnalogDevice>("mouse", mouseanalog); - mousemotion = std::make_shared<MouseMotionFactory>(mouse); - Input::RegisterFactory<Input::MotionDevice>("mouse", mousemotion); - mousetouch = std::make_shared<MouseTouchFactory>(mouse); - Input::RegisterFactory<Input::TouchDevice>("mouse", mousetouch); - - tas = std::make_shared<TasInput::Tas>(); - tasbuttons = std::make_shared<TasButtonFactory>(tas); - Input::RegisterFactory<Input::ButtonDevice>("tas", tasbuttons); - tasanalog = std::make_shared<TasAnalogFactory>(tas); - Input::RegisterFactory<Input::AnalogDevice>("tas", tasanalog); + Common::Input::RegisterFactory<Common::Input::InputDevice>( + "touch_from_button", std::make_shared<TouchFromButton>()); + Common::Input::RegisterFactory<Common::Input::InputDevice>( + "analog_from_button", std::make_shared<StickFromButton>()); } void Shutdown() { - Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); - Input::UnregisterFactory<Input::MotionDevice>("keyboard"); + Common::Input::UnregisterFactory<Common::Input::InputDevice>(keyboard->GetEngineName()); + Common::Input::UnregisterFactory<Common::Input::OutputDevice>(keyboard->GetEngineName()); keyboard.reset(); - Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); - Input::UnregisterFactory<Input::TouchDevice>("touch_from_button"); -#ifdef HAVE_SDL2 - sdl.reset(); -#endif - Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); - Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); - Input::UnregisterFactory<Input::VibrationDevice>("gcpad"); - gcbuttons.reset(); - gcanalog.reset(); - gcvibration.reset(); + Common::Input::UnregisterFactory<Common::Input::InputDevice>(mouse->GetEngineName()); + Common::Input::UnregisterFactory<Common::Input::OutputDevice>(mouse->GetEngineName()); + mouse.reset(); - Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); - Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); + Common::Input::UnregisterFactory<Common::Input::InputDevice>(touch_screen->GetEngineName()); + touch_screen.reset(); - udpmotion.reset(); - udptouch.reset(); + Common::Input::UnregisterFactory<Common::Input::InputDevice>(gcadapter->GetEngineName()); + Common::Input::UnregisterFactory<Common::Input::OutputDevice>(gcadapter->GetEngineName()); + gcadapter.reset(); - Input::UnregisterFactory<Input::ButtonDevice>("mouse"); - Input::UnregisterFactory<Input::AnalogDevice>("mouse"); - Input::UnregisterFactory<Input::MotionDevice>("mouse"); - Input::UnregisterFactory<Input::TouchDevice>("mouse"); + Common::Input::UnregisterFactory<Common::Input::InputDevice>(udp_client->GetEngineName()); + Common::Input::UnregisterFactory<Common::Input::OutputDevice>(udp_client->GetEngineName()); + udp_client.reset(); - mousebuttons.reset(); - mouseanalog.reset(); - mousemotion.reset(); - mousetouch.reset(); + Common::Input::UnregisterFactory<Common::Input::InputDevice>(tas_input->GetEngineName()); + Common::Input::UnregisterFactory<Common::Input::OutputDevice>(tas_input->GetEngineName()); + tas_input.reset(); - Input::UnregisterFactory<Input::ButtonDevice>("tas"); - Input::UnregisterFactory<Input::AnalogDevice>("tas"); +#ifdef HAVE_SDL2 + Common::Input::UnregisterFactory<Common::Input::InputDevice>(sdl->GetEngineName()); + Common::Input::UnregisterFactory<Common::Input::OutputDevice>(sdl->GetEngineName()); + sdl.reset(); +#endif - tasbuttons.reset(); - tasanalog.reset(); + Common::Input::UnregisterFactory<Common::Input::InputDevice>("touch_from_button"); + Common::Input::UnregisterFactory<Common::Input::InputDevice>("analog_from_button"); } [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { std::vector<Common::ParamPackage> devices = { - Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, - Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "keyboard"}}, + Common::ParamPackage{{"display", "Any"}, {"engine", "any"}}, }; - if (Settings::values.tas_enable) { - devices.emplace_back( - Common::ParamPackage{{"display", "TAS Controller"}, {"class", "tas"}}); - } + + auto keyboard_devices = keyboard->GetInputDevices(); + devices.insert(devices.end(), keyboard_devices.begin(), keyboard_devices.end()); + auto mouse_devices = mouse->GetInputDevices(); + devices.insert(devices.end(), mouse_devices.begin(), mouse_devices.end()); + auto gcadapter_devices = gcadapter->GetInputDevices(); + devices.insert(devices.end(), gcadapter_devices.begin(), gcadapter_devices.end()); + auto udp_devices = udp_client->GetInputDevices(); + devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); #ifdef HAVE_SDL2 auto sdl_devices = sdl->GetInputDevices(); devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); #endif - auto udp_devices = udp->GetInputDevices(); - devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); - auto gcpad_devices = gcadapter->GetInputDevices(); - devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end()); + return devices; } [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( const Common::ParamPackage& params) const { - if (!params.Has("class") || params.Get("class", "") == "any") { + if (!params.Has("engine") || params.Get("engine", "") == "any") { return {}; } - if (params.Get("class", "") == "gcpad") { + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return mouse->GetAnalogMappingForDevice(params); + } + if (engine == gcadapter->GetEngineName()) { return gcadapter->GetAnalogMappingForDevice(params); } - if (params.Get("class", "") == "tas") { - return tas->GetAnalogMappingForDevice(params); + if (engine == udp_client->GetEngineName()) { + return udp_client->GetAnalogMappingForDevice(params); + } + if (engine == tas_input->GetEngineName()) { + return tas_input->GetAnalogMappingForDevice(params); } #ifdef HAVE_SDL2 - if (params.Get("class", "") == "sdl") { + if (engine == sdl->GetEngineName()) { return sdl->GetAnalogMappingForDevice(params); } #endif @@ -152,17 +179,21 @@ struct InputSubsystem::Impl { [[nodiscard]] ButtonMapping GetButtonMappingForDevice( const Common::ParamPackage& params) const { - if (!params.Has("class") || params.Get("class", "") == "any") { + if (!params.Has("engine") || params.Get("engine", "") == "any") { return {}; } - if (params.Get("class", "") == "gcpad") { + const std::string engine = params.Get("engine", ""); + if (engine == gcadapter->GetEngineName()) { return gcadapter->GetButtonMappingForDevice(params); } - if (params.Get("class", "") == "tas") { - return tas->GetButtonMappingForDevice(params); + if (engine == udp_client->GetEngineName()) { + return udp_client->GetButtonMappingForDevice(params); + } + if (engine == tas_input->GetEngineName()) { + return tas_input->GetButtonMappingForDevice(params); } #ifdef HAVE_SDL2 - if (params.Get("class", "") == "sdl") { + if (engine == sdl->GetEngineName()) { return sdl->GetButtonMappingForDevice(params); } #endif @@ -171,40 +202,119 @@ struct InputSubsystem::Impl { [[nodiscard]] MotionMapping GetMotionMappingForDevice( const Common::ParamPackage& params) const { - if (!params.Has("class") || params.Get("class", "") == "any") { + if (!params.Has("engine") || params.Get("engine", "") == "any") { return {}; } - if (params.Get("class", "") == "cemuhookudp") { - // TODO return the correct motion device - return {}; + const std::string engine = params.Get("engine", ""); + if (engine == udp_client->GetEngineName()) { + return udp_client->GetMotionMappingForDevice(params); } #ifdef HAVE_SDL2 - if (params.Get("class", "") == "sdl") { + if (engine == sdl->GetEngineName()) { return sdl->GetMotionMappingForDevice(params); } #endif return {}; } + Common::Input::ButtonNames GetButtonName(const Common::ParamPackage& params) const { + if (!params.Has("engine") || params.Get("engine", "") == "any") { + return Common::Input::ButtonNames::Undefined; + } + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return mouse->GetUIName(params); + } + if (engine == gcadapter->GetEngineName()) { + return gcadapter->GetUIName(params); + } + if (engine == udp_client->GetEngineName()) { + return udp_client->GetUIName(params); + } + if (engine == tas_input->GetEngineName()) { + return tas_input->GetUIName(params); + } +#ifdef HAVE_SDL2 + if (engine == sdl->GetEngineName()) { + return sdl->GetUIName(params); + } +#endif + return Common::Input::ButtonNames::Invalid; + } + + bool IsController(const Common::ParamPackage& params) { + const std::string engine = params.Get("engine", ""); + if (engine == mouse->GetEngineName()) { + return true; + } + if (engine == gcadapter->GetEngineName()) { + return true; + } + if (engine == udp_client->GetEngineName()) { + return true; + } + if (engine == tas_input->GetEngineName()) { + return true; + } +#ifdef HAVE_SDL2 + if (engine == sdl->GetEngineName()) { + return true; + } +#endif + return false; + } + + void BeginConfiguration() { + keyboard->BeginConfiguration(); + mouse->BeginConfiguration(); + gcadapter->BeginConfiguration(); + udp_client->BeginConfiguration(); +#ifdef HAVE_SDL2 + sdl->BeginConfiguration(); +#endif + } + + void EndConfiguration() { + keyboard->EndConfiguration(); + mouse->EndConfiguration(); + gcadapter->EndConfiguration(); + udp_client->EndConfiguration(); +#ifdef HAVE_SDL2 + sdl->EndConfiguration(); +#endif + } + + void RegisterInput(MappingData data) { + mapping_factory->RegisterInput(data); + } + + std::shared_ptr<MappingFactory> mapping_factory; + std::shared_ptr<Keyboard> keyboard; + std::shared_ptr<Mouse> mouse; + std::shared_ptr<GCAdapter> gcadapter; + std::shared_ptr<TouchScreen> touch_screen; + std::shared_ptr<TasInput::Tas> tas_input; + std::shared_ptr<CemuhookUDP::UDPClient> udp_client; + + std::shared_ptr<InputFactory> keyboard_factory; + std::shared_ptr<InputFactory> mouse_factory; + std::shared_ptr<InputFactory> gcadapter_input_factory; + std::shared_ptr<InputFactory> touch_screen_factory; + std::shared_ptr<InputFactory> udp_client_input_factory; + std::shared_ptr<InputFactory> tas_input_factory; + + std::shared_ptr<OutputFactory> keyboard_output_factory; + std::shared_ptr<OutputFactory> mouse_output_factory; + std::shared_ptr<OutputFactory> gcadapter_output_factory; + std::shared_ptr<OutputFactory> udp_client_output_factory; + std::shared_ptr<OutputFactory> tas_output_factory; + #ifdef HAVE_SDL2 - std::unique_ptr<SDL::State> sdl; + std::shared_ptr<SDLDriver> sdl; + std::shared_ptr<InputFactory> sdl_input_factory; + std::shared_ptr<OutputFactory> sdl_output_factory; #endif - std::shared_ptr<GCButtonFactory> gcbuttons; - std::shared_ptr<GCAnalogFactory> gcanalog; - std::shared_ptr<GCVibrationFactory> gcvibration; - std::shared_ptr<UDPMotionFactory> udpmotion; - std::shared_ptr<UDPTouchFactory> udptouch; - std::shared_ptr<MouseButtonFactory> mousebuttons; - std::shared_ptr<MouseAnalogFactory> mouseanalog; - std::shared_ptr<MouseMotionFactory> mousemotion; - std::shared_ptr<MouseTouchFactory> mousetouch; - std::shared_ptr<TasButtonFactory> tasbuttons; - std::shared_ptr<TasAnalogFactory> tasanalog; - std::shared_ptr<CemuhookUDP::Client> udp; - std::shared_ptr<GCAdapter::Adapter> gcadapter; - std::shared_ptr<MouseInput::Mouse> mouse; - std::shared_ptr<TasInput::Tas> tas; }; InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} @@ -227,20 +337,28 @@ const Keyboard* InputSubsystem::GetKeyboard() const { return impl->keyboard.get(); } -MouseInput::Mouse* InputSubsystem::GetMouse() { +Mouse* InputSubsystem::GetMouse() { return impl->mouse.get(); } -const MouseInput::Mouse* InputSubsystem::GetMouse() const { +const Mouse* InputSubsystem::GetMouse() const { return impl->mouse.get(); } +TouchScreen* InputSubsystem::GetTouchScreen() { + return impl->touch_screen.get(); +} + +const TouchScreen* InputSubsystem::GetTouchScreen() const { + return impl->touch_screen.get(); +} + TasInput::Tas* InputSubsystem::GetTas() { - return impl->tas.get(); + return impl->tas_input.get(); } const TasInput::Tas* InputSubsystem::GetTas() const { - return impl->tas.get(); + return impl->tas_input.get(); } std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { @@ -259,100 +377,30 @@ MotionMapping InputSubsystem::GetMotionMappingForDevice(const Common::ParamPacka return impl->GetMotionMappingForDevice(device); } -GCAnalogFactory* InputSubsystem::GetGCAnalogs() { - return impl->gcanalog.get(); -} - -const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const { - return impl->gcanalog.get(); -} - -GCButtonFactory* InputSubsystem::GetGCButtons() { - return impl->gcbuttons.get(); -} - -const GCButtonFactory* InputSubsystem::GetGCButtons() const { - return impl->gcbuttons.get(); -} - -UDPMotionFactory* InputSubsystem::GetUDPMotions() { - return impl->udpmotion.get(); -} - -const UDPMotionFactory* InputSubsystem::GetUDPMotions() const { - return impl->udpmotion.get(); -} - -UDPTouchFactory* InputSubsystem::GetUDPTouch() { - return impl->udptouch.get(); -} - -const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { - return impl->udptouch.get(); -} - -MouseButtonFactory* InputSubsystem::GetMouseButtons() { - return impl->mousebuttons.get(); +Common::Input::ButtonNames InputSubsystem::GetButtonName(const Common::ParamPackage& params) const { + return impl->GetButtonName(params); } -const MouseButtonFactory* InputSubsystem::GetMouseButtons() const { - return impl->mousebuttons.get(); +bool InputSubsystem::IsController(const Common::ParamPackage& params) const { + return impl->IsController(params); } -MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() { - return impl->mouseanalog.get(); -} - -const MouseAnalogFactory* InputSubsystem::GetMouseAnalogs() const { - return impl->mouseanalog.get(); -} - -MouseMotionFactory* InputSubsystem::GetMouseMotions() { - return impl->mousemotion.get(); -} - -const MouseMotionFactory* InputSubsystem::GetMouseMotions() const { - return impl->mousemotion.get(); -} - -MouseTouchFactory* InputSubsystem::GetMouseTouch() { - return impl->mousetouch.get(); -} - -const MouseTouchFactory* InputSubsystem::GetMouseTouch() const { - return impl->mousetouch.get(); -} - -TasButtonFactory* InputSubsystem::GetTasButtons() { - return impl->tasbuttons.get(); -} - -const TasButtonFactory* InputSubsystem::GetTasButtons() const { - return impl->tasbuttons.get(); -} - -TasAnalogFactory* InputSubsystem::GetTasAnalogs() { - return impl->tasanalog.get(); +void InputSubsystem::ReloadInputDevices() { + impl->udp_client.get()->ReloadSockets(); } -const TasAnalogFactory* InputSubsystem::GetTasAnalogs() const { - return impl->tasanalog.get(); +void InputSubsystem::BeginMapping(Polling::InputType type) { + impl->BeginConfiguration(); + impl->mapping_factory->BeginMapping(type); } -void InputSubsystem::ReloadInputDevices() { - if (!impl->udp) { - return; - } - impl->udp->ReloadSockets(); +const Common::ParamPackage InputSubsystem::GetNextInput() const { + return impl->mapping_factory->GetNextInput(); } -std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( - [[maybe_unused]] Polling::DeviceType type) const { -#ifdef HAVE_SDL2 - return impl->sdl->GetPollers(type); -#else - return {}; -#endif +void InputSubsystem::StopMapping() const { + impl->EndConfiguration(); + impl->mapping_factory->StopMapping(); } std::string GenerateKeyboardParam(int key_code) { diff --git a/src/input_common/main.h b/src/input_common/main.h index 6390d3f09..c6f97f691 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -13,6 +13,10 @@ namespace Common { class ParamPackage; } +namespace Common::Input { +enum class ButtonNames; +} + namespace Settings::NativeAnalog { enum Values : int; } @@ -25,56 +29,26 @@ namespace Settings::NativeMotion { enum Values : int; } -namespace MouseInput { +namespace InputCommon { +class Keyboard; class Mouse; -} +class TouchScreen; +struct MappingData; +} // namespace InputCommon -namespace TasInput { +namespace InputCommon::TasInput { class Tas; -} +} // namespace InputCommon::TasInput namespace InputCommon { namespace Polling { - -enum class DeviceType { Button, AnalogPreferred, Motion }; - -/** - * A class that can be used to get inputs from an input device like controllers without having to - * poll the device's status yourself - */ -class DevicePoller { -public: - virtual ~DevicePoller() = default; - /// Setup and start polling for inputs, should be called before GetNextInput - /// If a device_id is provided, events should be filtered to only include events from this - /// device id - virtual void Start(const std::string& device_id = "") = 0; - /// Stop polling - virtual void Stop() = 0; - /** - * Every call to this function returns the next input recorded since calling Start - * @return A ParamPackage of the recorded input, which can be used to create an InputDevice. - * If there has been no input, the package is empty - */ - virtual Common::ParamPackage GetNextInput() = 0; -}; +/// Type of input desired for mapping purposes +enum class InputType { None, Button, Stick, Motion, Touch }; } // namespace Polling -class GCAnalogFactory; -class GCButtonFactory; -class UDPMotionFactory; -class UDPTouchFactory; -class MouseButtonFactory; -class MouseAnalogFactory; -class MouseMotionFactory; -class MouseTouchFactory; -class TasButtonFactory; -class TasAnalogFactory; -class Keyboard; - /** * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default - * mapping for the device. This is currently only implemented for the SDL backend devices. + * mapping for the device. */ using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; @@ -104,20 +78,27 @@ public: [[nodiscard]] const Keyboard* GetKeyboard() const; /// Retrieves the underlying mouse device. - [[nodiscard]] MouseInput::Mouse* GetMouse(); + [[nodiscard]] Mouse* GetMouse(); /// Retrieves the underlying mouse device. - [[nodiscard]] const MouseInput::Mouse* GetMouse() const; + [[nodiscard]] const Mouse* GetMouse() const; + + /// Retrieves the underlying touch screen device. + [[nodiscard]] TouchScreen* GetTouchScreen(); - /// Retrieves the underlying tas device. + /// Retrieves the underlying touch screen device. + [[nodiscard]] const TouchScreen* GetTouchScreen() const; + + /// Retrieves the underlying tas input device. [[nodiscard]] TasInput::Tas* GetTas(); - /// Retrieves the underlying tas device. + /// Retrieves the underlying tas input device. [[nodiscard]] const TasInput::Tas* GetTas() const; + /** * Returns all available input devices that this Factory can create a new device with. - * Each returned ParamPackage should have a `display` field used for display, a class field for - * backends to determine if this backend is meant to service the request and any other + * Each returned ParamPackage should have a `display` field used for display, a `engine` field + * for backends to determine if this backend is meant to service the request and any other * information needed to identify this in the backend later. */ [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const; @@ -131,83 +112,34 @@ public: /// Retrieves the motion mappings for the given device. [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; - /// Retrieves the underlying GameCube analog handler. - [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); + /// Returns an enum contaning the name to be displayed from the input engine. + [[nodiscard]] Common::Input::ButtonNames GetButtonName( + const Common::ParamPackage& params) const; - /// Retrieves the underlying GameCube analog handler. - [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; + /// Returns true if device is a controller. + [[nodiscard]] bool IsController(const Common::ParamPackage& params) const; - /// Retrieves the underlying GameCube button handler. - [[nodiscard]] GCButtonFactory* GetGCButtons(); - - /// Retrieves the underlying GameCube button handler. - [[nodiscard]] const GCButtonFactory* GetGCButtons() const; - - /// Retrieves the underlying udp motion handler. - [[nodiscard]] UDPMotionFactory* GetUDPMotions(); - - /// Retrieves the underlying udp motion handler. - [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const; - - /// Retrieves the underlying udp touch handler. - [[nodiscard]] UDPTouchFactory* GetUDPTouch(); - - /// Retrieves the underlying udp touch handler. - [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; - - /// Retrieves the underlying mouse button handler. - [[nodiscard]] MouseButtonFactory* GetMouseButtons(); - - /// Retrieves the underlying mouse button handler. - [[nodiscard]] const MouseButtonFactory* GetMouseButtons() const; - - /// Retrieves the underlying mouse analog handler. - [[nodiscard]] MouseAnalogFactory* GetMouseAnalogs(); - - /// Retrieves the underlying mouse analog handler. - [[nodiscard]] const MouseAnalogFactory* GetMouseAnalogs() const; - - /// Retrieves the underlying mouse motion handler. - [[nodiscard]] MouseMotionFactory* GetMouseMotions(); - - /// Retrieves the underlying mouse motion handler. - [[nodiscard]] const MouseMotionFactory* GetMouseMotions() const; - - /// Retrieves the underlying mouse touch handler. - [[nodiscard]] MouseTouchFactory* GetMouseTouch(); - - /// Retrieves the underlying mouse touch handler. - [[nodiscard]] const MouseTouchFactory* GetMouseTouch() const; - - /// Retrieves the underlying tas button handler. - [[nodiscard]] TasButtonFactory* GetTasButtons(); - - /// Retrieves the underlying tas button handler. - [[nodiscard]] const TasButtonFactory* GetTasButtons() const; - - /// Retrieves the underlying tas analogs handler. - [[nodiscard]] TasAnalogFactory* GetTasAnalogs(); + /// Reloads the input devices. + void ReloadInputDevices(); - /// Retrieves the underlying tas analogs handler. - [[nodiscard]] const TasAnalogFactory* GetTasAnalogs() const; + /// Start polling from all backends for a desired input type. + void BeginMapping(Polling::InputType type); - /// Reloads the input devices - void ReloadInputDevices(); + /// Returns an input event with mapping information. + [[nodiscard]] const Common::ParamPackage GetNextInput() const; - /// Get all DevicePoller from all backends for a specific device type - [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( - Polling::DeviceType type) const; + /// Stop polling from all backends. + void StopMapping() const; private: struct Impl; std::unique_ptr<Impl> impl; }; -/// Generates a serialized param package for creating a keyboard button device +/// Generates a serialized param package for creating a keyboard button device. std::string GenerateKeyboardParam(int key_code); -/// Generates a serialized param package for creating an analog device taking input from keyboard +/// Generates a serialized param package for creating an analog device taking input from keyboard. std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, int key_modifier, float modifier_scale); - } // namespace InputCommon diff --git a/src/input_common/motion_from_button.cpp b/src/input_common/motion_from_button.cpp deleted file mode 100644 index 29045a673..000000000 --- a/src/input_common/motion_from_button.cpp +++ /dev/null @@ -1,34 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "input_common/motion_from_button.h" -#include "input_common/motion_input.h" - -namespace InputCommon { - -class MotionKey final : public Input::MotionDevice { -public: - using Button = std::unique_ptr<Input::ButtonDevice>; - - explicit MotionKey(Button key_) : key(std::move(key_)) {} - - Input::MotionStatus GetStatus() const override { - - if (key->GetStatus()) { - return motion.GetRandomMotion(2, 6); - } - return motion.GetRandomMotion(0, 0); - } - -private: - Button key; - InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; -}; - -std::unique_ptr<Input::MotionDevice> MotionFromButton::Create(const Common::ParamPackage& params) { - auto key = Input::CreateDevice<Input::ButtonDevice>(params.Serialize()); - return std::make_unique<MotionKey>(std::move(key)); -} - -} // namespace InputCommon diff --git a/src/input_common/motion_from_button.h b/src/input_common/motion_from_button.h deleted file mode 100644 index a959046fb..000000000 --- a/src/input_common/motion_from_button.h +++ /dev/null @@ -1,25 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include "core/frontend/input.h" - -namespace InputCommon { - -/** - * An motion device factory that takes a keyboard button and uses it as a random - * motion device. - */ -class MotionFromButton final : public Input::Factory<Input::MotionDevice> { -public: - /** - * Creates an motion device from button devices - * @param params contains parameters for creating the device: - * - "key": a serialized ParamPackage for creating a button device - */ - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; -}; - -} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_input.cpp b/src/input_common/mouse/mouse_input.cpp deleted file mode 100644 index 3b052ffb2..000000000 --- a/src/input_common/mouse/mouse_input.cpp +++ /dev/null @@ -1,223 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include <stop_token> -#include <thread> - -#include "common/settings.h" -#include "input_common/mouse/mouse_input.h" - -namespace MouseInput { - -Mouse::Mouse() { - update_thread = std::jthread([this](std::stop_token stop_token) { UpdateThread(stop_token); }); -} - -Mouse::~Mouse() = default; - -void Mouse::UpdateThread(std::stop_token stop_token) { - constexpr int update_time = 10; - while (!stop_token.stop_requested()) { - for (MouseInfo& info : mouse_info) { - const Common::Vec3f angular_direction{ - -info.tilt_direction.y, - 0.0f, - -info.tilt_direction.x, - }; - - info.motion.SetGyroscope(angular_direction * info.tilt_speed); - info.motion.UpdateRotation(update_time * 1000); - info.motion.UpdateOrientation(update_time * 1000); - info.tilt_speed = 0; - info.data.motion = info.motion.GetMotion(); - if (Settings::values.mouse_panning) { - info.last_mouse_change *= 0.96f; - info.data.axis = {static_cast<int>(16 * info.last_mouse_change.x), - static_cast<int>(16 * -info.last_mouse_change.y)}; - } - } - if (configuring) { - UpdateYuzuSettings(); - } - if (mouse_panning_timout++ > 20) { - StopPanning(); - } - std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); - } -} - -void Mouse::UpdateYuzuSettings() { - if (buttons == 0) { - return; - } - - mouse_queue.Push(MouseStatus{ - .button = last_button, - }); -} - -void Mouse::PressButton(int x, int y, MouseButton button_) { - const auto button_index = static_cast<std::size_t>(button_); - if (button_index >= mouse_info.size()) { - return; - } - - const auto button = 1U << button_index; - buttons |= static_cast<u16>(button); - last_button = button_; - - mouse_info[button_index].mouse_origin = Common::MakeVec(x, y); - mouse_info[button_index].last_mouse_position = Common::MakeVec(x, y); - mouse_info[button_index].data.pressed = true; -} - -void Mouse::StopPanning() { - for (MouseInfo& info : mouse_info) { - if (Settings::values.mouse_panning) { - info.data.axis = {}; - info.tilt_speed = 0; - info.last_mouse_change = {}; - } - } -} - -void Mouse::MouseMove(int x, int y, int center_x, int center_y) { - for (MouseInfo& info : mouse_info) { - if (Settings::values.mouse_panning) { - auto mouse_change = - (Common::MakeVec(x, y) - Common::MakeVec(center_x, center_y)).Cast<float>(); - mouse_panning_timout = 0; - - if (mouse_change.y == 0 && mouse_change.x == 0) { - continue; - } - const auto mouse_change_length = mouse_change.Length(); - if (mouse_change_length < 3.0f) { - mouse_change /= mouse_change_length / 3.0f; - } - - info.last_mouse_change = (info.last_mouse_change * 0.91f) + (mouse_change * 0.09f); - - const auto last_mouse_change_length = info.last_mouse_change.Length(); - if (last_mouse_change_length > 8.0f) { - info.last_mouse_change /= last_mouse_change_length / 8.0f; - } else if (last_mouse_change_length < 1.0f) { - info.last_mouse_change = mouse_change / mouse_change.Length(); - } - - info.tilt_direction = info.last_mouse_change; - info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; - continue; - } - - if (info.data.pressed) { - const auto mouse_move = Common::MakeVec(x, y) - info.mouse_origin; - const auto mouse_change = Common::MakeVec(x, y) - info.last_mouse_position; - info.last_mouse_position = Common::MakeVec(x, y); - info.data.axis = {mouse_move.x, -mouse_move.y}; - - if (mouse_change.x == 0 && mouse_change.y == 0) { - info.tilt_speed = 0; - } else { - info.tilt_direction = mouse_change.Cast<float>(); - info.tilt_speed = info.tilt_direction.Normalize() * info.sensitivity; - } - } - } -} - -void Mouse::ReleaseButton(MouseButton button_) { - const auto button_index = static_cast<std::size_t>(button_); - if (button_index >= mouse_info.size()) { - return; - } - - const auto button = 1U << button_index; - buttons &= static_cast<u16>(0xFF - button); - - mouse_info[button_index].tilt_speed = 0; - mouse_info[button_index].data.pressed = false; - mouse_info[button_index].data.axis = {0, 0}; -} - -void Mouse::ReleaseAllButtons() { - buttons = 0; - for (auto& info : mouse_info) { - info.tilt_speed = 0; - info.data.pressed = false; - info.data.axis = {0, 0}; - } -} - -void Mouse::BeginConfiguration() { - buttons = 0; - last_button = MouseButton::Undefined; - mouse_queue.Clear(); - configuring = true; -} - -void Mouse::EndConfiguration() { - buttons = 0; - for (MouseInfo& info : mouse_info) { - info.tilt_speed = 0; - info.data.pressed = false; - info.data.axis = {0, 0}; - } - last_button = MouseButton::Undefined; - mouse_queue.Clear(); - configuring = false; -} - -bool Mouse::ToggleButton(std::size_t button_) { - if (button_ >= mouse_info.size()) { - return false; - } - const auto button = 1U << button_; - const bool button_state = (toggle_buttons & button) != 0; - const bool button_lock = (lock_buttons & button) != 0; - - if (button_lock) { - return button_state; - } - - lock_buttons |= static_cast<u16>(button); - - if (button_state) { - toggle_buttons &= static_cast<u16>(0xFF - button); - } else { - toggle_buttons |= static_cast<u16>(button); - } - - return !button_state; -} - -bool Mouse::UnlockButton(std::size_t button_) { - if (button_ >= mouse_info.size()) { - return false; - } - - const auto button = 1U << button_; - const bool button_state = (toggle_buttons & button) != 0; - - lock_buttons &= static_cast<u16>(0xFF - button); - - return button_state; -} - -Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() { - return mouse_queue; -} - -const Common::SPSCQueue<MouseStatus>& Mouse::GetMouseQueue() const { - return mouse_queue; -} - -MouseData& Mouse::GetMouseState(std::size_t button) { - return mouse_info[button].data; -} - -const MouseData& Mouse::GetMouseState(std::size_t button) const { - return mouse_info[button].data; -} -} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_input.h b/src/input_common/mouse/mouse_input.h deleted file mode 100644 index c8bae99c1..000000000 --- a/src/input_common/mouse/mouse_input.h +++ /dev/null @@ -1,116 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> -#include <mutex> -#include <stop_token> -#include <thread> - -#include "common/common_types.h" -#include "common/threadsafe_queue.h" -#include "common/vector_math.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" - -namespace MouseInput { - -enum class MouseButton { - Left, - Right, - Wheel, - Backward, - Forward, - Task, - Extra, - Undefined, -}; - -struct MouseStatus { - MouseButton button{MouseButton::Undefined}; -}; - -struct MouseData { - bool pressed{}; - std::array<int, 2> axis{}; - Input::MotionStatus motion{}; - Input::TouchStatus touch{}; -}; - -class Mouse { -public: - Mouse(); - ~Mouse(); - - /// Used for polling - void BeginConfiguration(); - void EndConfiguration(); - - /** - * Signals that a button is pressed. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - * @param button_ the button pressed - */ - void PressButton(int x, int y, MouseButton button_); - - /** - * Signals that mouse has moved. - * @param x the x-coordinate of the cursor - * @param y the y-coordinate of the cursor - * @param center_x the x-coordinate of the middle of the screen - * @param center_y the y-coordinate of the middle of the screen - */ - void MouseMove(int x, int y, int center_x, int center_y); - - /** - * Signals that a button is released. - * @param button_ the button pressed - */ - void ReleaseButton(MouseButton button_); - - /** - * Signals that all buttons are released - */ - void ReleaseAllButtons(); - - [[nodiscard]] bool ToggleButton(std::size_t button_); - [[nodiscard]] bool UnlockButton(std::size_t button_); - - [[nodiscard]] Common::SPSCQueue<MouseStatus>& GetMouseQueue(); - [[nodiscard]] const Common::SPSCQueue<MouseStatus>& GetMouseQueue() const; - - [[nodiscard]] MouseData& GetMouseState(std::size_t button); - [[nodiscard]] const MouseData& GetMouseState(std::size_t button) const; - -private: - void UpdateThread(std::stop_token stop_token); - void UpdateYuzuSettings(); - void StopPanning(); - - struct MouseInfo { - InputCommon::MotionInput motion{0.0f, 0.0f, 0.0f}; - Common::Vec2<int> mouse_origin; - Common::Vec2<int> last_mouse_position; - Common::Vec2<float> last_mouse_change; - bool is_tilting = false; - float sensitivity{0.120f}; - - float tilt_speed = 0; - Common::Vec2<float> tilt_direction; - MouseData data; - }; - - u16 buttons{}; - u16 toggle_buttons{}; - u16 lock_buttons{}; - std::jthread update_thread; - MouseButton last_button{MouseButton::Undefined}; - std::array<MouseInfo, 7> mouse_info; - Common::SPSCQueue<MouseStatus> mouse_queue; - bool configuring{false}; - int mouse_panning_timout{}; -}; -} // namespace MouseInput diff --git a/src/input_common/mouse/mouse_poller.cpp b/src/input_common/mouse/mouse_poller.cpp deleted file mode 100644 index 090b26972..000000000 --- a/src/input_common/mouse/mouse_poller.cpp +++ /dev/null @@ -1,299 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <memory> -#include <mutex> -#include <utility> - -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "input_common/mouse/mouse_input.h" -#include "input_common/mouse/mouse_poller.h" - -namespace InputCommon { - -class MouseButton final : public Input::ButtonDevice { -public: - explicit MouseButton(u32 button_, bool toggle_, MouseInput::Mouse* mouse_input_) - : button(button_), toggle(toggle_), mouse_input(mouse_input_) {} - - bool GetStatus() const override { - const bool button_state = mouse_input->GetMouseState(button).pressed; - if (!toggle) { - return button_state; - } - - if (button_state) { - return mouse_input->ToggleButton(button); - } - return mouse_input->UnlockButton(button); - } - -private: - const u32 button; - const bool toggle; - MouseInput::Mouse* mouse_input; -}; - -MouseButtonFactory::MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -std::unique_ptr<Input::ButtonDevice> MouseButtonFactory::Create( - const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - const auto toggle = params.Get("toggle", false); - - return std::make_unique<MouseButton>(button_id, toggle, mouse_input.get()); -} - -Common::ParamPackage MouseButtonFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("button", static_cast<u16>(pad.button)); - params.Set("toggle", false); - return params; - } - } - return params; -} - -void MouseButtonFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseButtonFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -class MouseAnalog final : public Input::AnalogDevice { -public: - explicit MouseAnalog(u32 port_, u32 axis_x_, u32 axis_y_, bool invert_x_, bool invert_y_, - float deadzone_, float range_, const MouseInput::Mouse* mouse_input_) - : button(port_), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), invert_y(invert_y_), - deadzone(deadzone_), range(range_), mouse_input(mouse_input_) {} - - float GetAxis(u32 axis) const { - std::lock_guard lock{mutex}; - const auto axis_value = - static_cast<float>(mouse_input->GetMouseState(button).axis.at(axis)); - const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.10f; - return axis_value * sensitivity / (100.0f * range); - } - - std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - if (invert_x) { - x = -x; - } - if (invert_y) { - y = -y; - } - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple<float, float> GetStatus() const override { - const auto [x, y] = GetAnalog(axis_x, axis_y); - const float r = std::sqrt((x * x) + (y * y)); - if (r > deadzone) { - return {x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)}; - } - return {0.0f, 0.0f}; - } - - std::tuple<float, float> GetRawStatus() const override { - const float x = GetAxis(axis_x); - const float y = GetAxis(axis_y); - return {x, y}; - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {deadzone, range, 0.5f}; - } - -private: - const u32 button; - const u32 axis_x; - const u32 axis_y; - const bool invert_x; - const bool invert_y; - const float deadzone; - const float range; - const MouseInput::Mouse* mouse_input; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -MouseAnalogFactory::MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr<Input::AnalogDevice> MouseAnalogFactory::Create( - const Common::ParamPackage& params) { - const auto port = static_cast<u32>(params.Get("port", 0)); - const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); - const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); - const auto deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const auto range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - const std::string invert_x_value = params.Get("invert_x", "+"); - const std::string invert_y_value = params.Get("invert_y", "+"); - const bool invert_x = invert_x_value == "-"; - const bool invert_y = invert_y_value == "-"; - - return std::make_unique<MouseAnalog>(port, axis_x, axis_y, invert_x, invert_y, deadzone, range, - mouse_input.get()); -} - -void MouseAnalogFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseAnalogFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -Common::ParamPackage MouseAnalogFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("port", static_cast<u16>(pad.button)); - params.Set("axis_x", 0); - params.Set("axis_y", 1); - params.Set("invert_x", "+"); - params.Set("invert_y", "+"); - return params; - } - } - return params; -} - -class MouseMotion final : public Input::MotionDevice { -public: - explicit MouseMotion(u32 button_, const MouseInput::Mouse* mouse_input_) - : button(button_), mouse_input(mouse_input_) {} - - Input::MotionStatus GetStatus() const override { - return mouse_input->GetMouseState(button).motion; - } - -private: - const u32 button; - const MouseInput::Mouse* mouse_input; -}; - -MouseMotionFactory::MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -std::unique_ptr<Input::MotionDevice> MouseMotionFactory::Create( - const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - - return std::make_unique<MouseMotion>(button_id, mouse_input.get()); -} - -Common::ParamPackage MouseMotionFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("button", static_cast<u16>(pad.button)); - return params; - } - } - return params; -} - -void MouseMotionFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseMotionFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -class MouseTouch final : public Input::TouchDevice { -public: - explicit MouseTouch(u32 button_, const MouseInput::Mouse* mouse_input_) - : button(button_), mouse_input(mouse_input_) {} - - Input::TouchStatus GetStatus() const override { - return mouse_input->GetMouseState(button).touch; - } - -private: - const u32 button; - const MouseInput::Mouse* mouse_input; -}; - -MouseTouchFactory::MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_) - : mouse_input(std::move(mouse_input_)) {} - -std::unique_ptr<Input::TouchDevice> MouseTouchFactory::Create(const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - - return std::make_unique<MouseTouch>(button_id, mouse_input.get()); -} - -Common::ParamPackage MouseTouchFactory::GetNextInput() const { - MouseInput::MouseStatus pad; - Common::ParamPackage params; - auto& queue = mouse_input->GetMouseQueue(); - while (queue.Pop(pad)) { - // This while loop will break on the earliest detected button - if (pad.button != MouseInput::MouseButton::Undefined) { - params.Set("engine", "mouse"); - params.Set("button", static_cast<u16>(pad.button)); - return params; - } - } - return params; -} - -void MouseTouchFactory::BeginConfiguration() { - polling = true; - mouse_input->BeginConfiguration(); -} - -void MouseTouchFactory::EndConfiguration() { - polling = false; - mouse_input->EndConfiguration(); -} - -} // namespace InputCommon diff --git a/src/input_common/mouse/mouse_poller.h b/src/input_common/mouse/mouse_poller.h deleted file mode 100644 index cf331293b..000000000 --- a/src/input_common/mouse/mouse_poller.h +++ /dev/null @@ -1,109 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "core/frontend/input.h" -#include "input_common/mouse/mouse_input.h" - -namespace InputCommon { - -/** - * A button device factory representing a mouse. It receives mouse events and forward them - * to all button devices it created. - */ -class MouseButtonFactory final : public Input::Factory<Input::ButtonDevice> { -public: - explicit MouseButtonFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); - - /** - * Creates a button device from a button press - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<MouseInput::Mouse> mouse_input; - bool polling = false; -}; - -/// An analog device factory that creates analog devices from mouse -class MouseAnalogFactory final : public Input::Factory<Input::AnalogDevice> { -public: - explicit MouseAnalogFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); - - std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<MouseInput::Mouse> mouse_input; - bool polling = false; -}; - -/// A motion device factory that creates motion devices from mouse -class MouseMotionFactory final : public Input::Factory<Input::MotionDevice> { -public: - explicit MouseMotionFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); - - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<MouseInput::Mouse> mouse_input; - bool polling = false; -}; - -/// An touch device factory that creates touch devices from mouse -class MouseTouchFactory final : public Input::Factory<Input::TouchDevice> { -public: - explicit MouseTouchFactory(std::shared_ptr<MouseInput::Mouse> mouse_input_); - - std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput() const; - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<MouseInput::Mouse> mouse_input; - bool polling = false; -}; - -} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.cpp b/src/input_common/sdl/sdl.cpp deleted file mode 100644 index 644db3448..000000000 --- a/src/input_common/sdl/sdl.cpp +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include "input_common/sdl/sdl.h" -#ifdef HAVE_SDL2 -#include "input_common/sdl/sdl_impl.h" -#endif - -namespace InputCommon::SDL { - -std::unique_ptr<State> Init() { -#ifdef HAVE_SDL2 - return std::make_unique<SDLState>(); -#else - return std::make_unique<NullState>(); -#endif -} -} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h deleted file mode 100644 index b5d41bba4..000000000 --- a/src/input_common/sdl/sdl.h +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include <vector> -#include "common/param_package.h" -#include "input_common/main.h" - -namespace InputCommon::Polling { -class DevicePoller; -enum class DeviceType; -} // namespace InputCommon::Polling - -namespace InputCommon::SDL { - -class State { -public: - using Pollers = std::vector<std::unique_ptr<Polling::DevicePoller>>; - - /// Unregisters SDL device factories and shut them down. - virtual ~State() = default; - - virtual Pollers GetPollers(Polling::DeviceType) { - return {}; - } - - virtual std::vector<Common::ParamPackage> GetInputDevices() { - return {}; - } - - virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) { - return {}; - } - virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { - return {}; - } - virtual MotionMapping GetMotionMappingForDevice(const Common::ParamPackage&) { - return {}; - } -}; - -class NullState : public State { -public: -}; - -std::unique_ptr<State> Init(); - -} // namespace InputCommon::SDL diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp deleted file mode 100644 index ecb00d428..000000000 --- a/src/input_common/sdl/sdl_impl.cpp +++ /dev/null @@ -1,1658 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include <array> -#include <atomic> -#include <chrono> -#include <cmath> -#include <functional> -#include <mutex> -#include <optional> -#include <sstream> -#include <string> -#include <thread> -#include <tuple> -#include <unordered_map> -#include <utility> -#include <vector> - -#include "common/logging/log.h" -#include "common/math_util.h" -#include "common/param_package.h" -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "core/frontend/input.h" -#include "input_common/motion_input.h" -#include "input_common/sdl/sdl_impl.h" - -namespace InputCommon::SDL { - -namespace { -std::string GetGUID(SDL_Joystick* joystick) { - const SDL_JoystickGUID guid = SDL_JoystickGetGUID(joystick); - char guid_str[33]; - SDL_JoystickGetGUIDString(guid, guid_str, sizeof(guid_str)); - return guid_str; -} - -/// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); -} // Anonymous namespace - -static int SDLEventWatcher(void* user_data, SDL_Event* event) { - auto* const sdl_state = static_cast<SDLState*>(user_data); - - // Don't handle the event if we are configuring - if (sdl_state->polling) { - sdl_state->event_queue.Push(*event); - } else { - sdl_state->HandleGameControllerEvent(*event); - } - - return 0; -} - -class SDLJoystick { -public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, - SDL_GameController* game_controller) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, - sdl_controller{game_controller, &SDL_GameControllerClose} { - EnableMotion(); - } - - void EnableMotion() { - if (sdl_controller) { - SDL_GameController* controller = sdl_controller.get(); - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); - has_accel = true; - } - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { - SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); - has_gyro = true; - } - } - } - - void SetButton(int button, bool value) { - std::lock_guard lock{mutex}; - state.buttons.insert_or_assign(button, value); - } - - void PreSetButton(int button) { - if (!state.buttons.contains(button)) { - SetButton(button, false); - } - } - - void SetMotion(SDL_ControllerSensorEvent event) { - constexpr float gravity_constant = 9.80665f; - std::lock_guard lock{mutex}; - u64 time_difference = event.timestamp - last_motion_update; - last_motion_update = event.timestamp; - switch (event.sensor) { - case SDL_SENSOR_ACCEL: { - const Common::Vec3f acceleration = {-event.data[0], event.data[2], -event.data[1]}; - motion.SetAcceleration(acceleration / gravity_constant); - break; - } - case SDL_SENSOR_GYRO: { - const Common::Vec3f gyroscope = {event.data[0], -event.data[2], event.data[1]}; - motion.SetGyroscope(gyroscope / (Common::PI * 2)); - break; - } - } - - // Ignore duplicated timestamps - if (time_difference == 0) { - return; - } - - motion.SetGyroThreshold(0.0001f); - motion.UpdateRotation(time_difference * 1000); - motion.UpdateOrientation(time_difference * 1000); - } - - bool GetButton(int button) const { - std::lock_guard lock{mutex}; - return state.buttons.at(button); - } - - bool ToggleButton(int button) { - std::lock_guard lock{mutex}; - - if (!state.toggle_buttons.contains(button) || !state.lock_buttons.contains(button)) { - state.toggle_buttons.insert_or_assign(button, false); - state.lock_buttons.insert_or_assign(button, false); - } - - const bool button_state = state.toggle_buttons.at(button); - const bool button_lock = state.lock_buttons.at(button); - - if (button_lock) { - return button_state; - } - - state.lock_buttons.insert_or_assign(button, true); - - if (button_state) { - state.toggle_buttons.insert_or_assign(button, false); - } else { - state.toggle_buttons.insert_or_assign(button, true); - } - - return !button_state; - } - - bool UnlockButton(int button) { - std::lock_guard lock{mutex}; - if (!state.toggle_buttons.contains(button)) { - return false; - } - state.lock_buttons.insert_or_assign(button, false); - return state.toggle_buttons.at(button); - } - - void SetAxis(int axis, Sint16 value) { - std::lock_guard lock{mutex}; - state.axes.insert_or_assign(axis, value); - } - - void PreSetAxis(int axis) { - if (!state.axes.contains(axis)) { - SetAxis(axis, 0); - } - } - - float GetAxis(int axis, float range, float offset) const { - std::lock_guard lock{mutex}; - const float value = static_cast<float>(state.axes.at(axis)) / 32767.0f; - const float offset_scale = (value + offset) > 0.0f ? 1.0f + offset : 1.0f - offset; - return (value + offset) / range / offset_scale; - } - - bool RumblePlay(u16 amp_low, u16 amp_high) { - constexpr u32 rumble_max_duration_ms = 1000; - - if (sdl_controller) { - return SDL_GameControllerRumble(sdl_controller.get(), amp_low, amp_high, - rumble_max_duration_ms) != -1; - } else if (sdl_joystick) { - return SDL_JoystickRumble(sdl_joystick.get(), amp_low, amp_high, - rumble_max_duration_ms) != -1; - } - - return false; - } - - std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range, float offset_x, - float offset_y) const { - float x = GetAxis(axis_x, range, offset_x); - float y = GetAxis(axis_y, range, offset_y); - y = -y; // 3DS uses an y-axis inverse from SDL - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return std::make_tuple(x, y); - } - - bool HasGyro() const { - return has_gyro; - } - - bool HasAccel() const { - return has_accel; - } - - const MotionInput& GetMotion() const { - return motion; - } - - void SetHat(int hat, Uint8 direction) { - std::lock_guard lock{mutex}; - state.hats.insert_or_assign(hat, direction); - } - - bool GetHatDirection(int hat, Uint8 direction) const { - std::lock_guard lock{mutex}; - return (state.hats.at(hat) & direction) != 0; - } - /** - * The guid of the joystick - */ - const std::string& GetGUID() const { - return guid; - } - - /** - * The number of joystick from the same type that were connected before this joystick - */ - int GetPort() const { - return port; - } - - SDL_Joystick* GetSDLJoystick() const { - return sdl_joystick.get(); - } - - SDL_GameController* GetSDLGameController() const { - return sdl_controller.get(); - } - - void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { - sdl_joystick.reset(joystick); - sdl_controller.reset(controller); - } - - bool IsJoyconLeft() const { - const std::string controller_name = GetControllerName(); - if (std::strstr(controller_name.c_str(), "Joy-Con Left") != nullptr) { - return true; - } - if (std::strstr(controller_name.c_str(), "Joy-Con (L)") != nullptr) { - return true; - } - return false; - } - - bool IsJoyconRight() const { - const std::string controller_name = GetControllerName(); - if (std::strstr(controller_name.c_str(), "Joy-Con Right") != nullptr) { - return true; - } - if (std::strstr(controller_name.c_str(), "Joy-Con (R)") != nullptr) { - return true; - } - return false; - } - - std::string GetControllerName() const { - if (sdl_controller) { - switch (SDL_GameControllerGetType(sdl_controller.get())) { - case SDL_CONTROLLER_TYPE_XBOX360: - return "XBox 360 Controller"; - case SDL_CONTROLLER_TYPE_XBOXONE: - return "XBox One Controller"; - default: - break; - } - const auto name = SDL_GameControllerName(sdl_controller.get()); - if (name) { - return name; - } - } - - if (sdl_joystick) { - const auto name = SDL_JoystickName(sdl_joystick.get()); - if (name) { - return name; - } - } - - return "Unknown"; - } - -private: - struct State { - std::unordered_map<int, bool> buttons; - std::unordered_map<int, bool> toggle_buttons{}; - std::unordered_map<int, bool> lock_buttons{}; - std::unordered_map<int, Sint16> axes; - std::unordered_map<int, Uint8> hats; - } state; - std::string guid; - int port; - std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; - std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; - mutable std::mutex mutex; - - // Motion is initialized with the PID values - MotionInput motion{0.3f, 0.005f, 0.0f}; - u64 last_motion_update{}; - bool has_gyro{false}; - bool has_accel{false}; -}; - -std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& guid, int port) { - std::lock_guard lock{joystick_map_mutex}; - const auto it = joystick_map.find(guid); - - if (it != joystick_map.end()) { - while (it->second.size() <= static_cast<std::size_t>(port)) { - auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), - nullptr, nullptr); - it->second.emplace_back(std::move(joystick)); - } - - return it->second[static_cast<std::size_t>(port)]; - } - - auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr); - - return joystick_map[guid].emplace_back(std::move(joystick)); -} - -std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { - auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - const auto map_it = joystick_map.find(guid); - - if (map_it == joystick_map.end()) { - return nullptr; - } - - const auto vec_it = std::find_if(map_it->second.begin(), map_it->second.end(), - [&sdl_joystick](const auto& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - - if (vec_it == map_it->second.end()) { - return nullptr; - } - - return *vec_it; -} - -void SDLState::InitJoystick(int joystick_index) { - SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); - SDL_GameController* sdl_gamecontroller = nullptr; - - if (SDL_IsGameController(joystick_index)) { - sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); - } - - if (!sdl_joystick) { - LOG_ERROR(Input, "Failed to open joystick {}", joystick_index); - return; - } - - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); - joystick_map[guid].emplace_back(std::move(joystick)); - return; - } - - auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = - std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [](const auto& joystick) { return !joystick->GetSDLJoystick(); }); - - if (joystick_it != joystick_guid_list.end()) { - (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); - return; - } - - const int port = static_cast<int>(joystick_guid_list.size()); - auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); - joystick_guid_list.emplace_back(std::move(joystick)); -} - -void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { - const std::string guid = GetGUID(sdl_joystick); - - std::lock_guard lock{joystick_map_mutex}; - // This call to guid is safe since the joystick is guaranteed to be in the map - const auto& joystick_guid_list = joystick_map[guid]; - const auto joystick_it = std::find_if(joystick_guid_list.begin(), joystick_guid_list.end(), - [&sdl_joystick](const auto& joystick) { - return joystick->GetSDLJoystick() == sdl_joystick; - }); - - if (joystick_it != joystick_guid_list.end()) { - (*joystick_it)->SetSDLJoystick(nullptr, nullptr); - } -} - -void SDLState::HandleGameControllerEvent(const SDL_Event& event) { - switch (event.type) { - case SDL_JOYBUTTONUP: { - if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { - joystick->SetButton(event.jbutton.button, false); - } - break; - } - case SDL_JOYBUTTONDOWN: { - if (auto joystick = GetSDLJoystickBySDLID(event.jbutton.which)) { - joystick->SetButton(event.jbutton.button, true); - } - break; - } - case SDL_JOYHATMOTION: { - if (auto joystick = GetSDLJoystickBySDLID(event.jhat.which)) { - joystick->SetHat(event.jhat.hat, event.jhat.value); - } - break; - } - case SDL_JOYAXISMOTION: { - if (auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { - joystick->SetAxis(event.jaxis.axis, event.jaxis.value); - } - break; - } - case SDL_CONTROLLERSENSORUPDATE: { - if (auto joystick = GetSDLJoystickBySDLID(event.csensor.which)) { - joystick->SetMotion(event.csensor); - } - break; - } - case SDL_JOYDEVICEREMOVED: - LOG_DEBUG(Input, "Controller removed with Instance_ID {}", event.jdevice.which); - CloseJoystick(SDL_JoystickFromInstanceID(event.jdevice.which)); - break; - case SDL_JOYDEVICEADDED: - LOG_DEBUG(Input, "Controller connected with device index {}", event.jdevice.which); - InitJoystick(event.jdevice.which); - break; - } -} - -void SDLState::CloseJoysticks() { - std::lock_guard lock{joystick_map_mutex}; - joystick_map.clear(); -} - -class SDLButton final : public Input::ButtonDevice { -public: - explicit SDLButton(std::shared_ptr<SDLJoystick> joystick_, int button_, bool toggle_) - : joystick(std::move(joystick_)), button(button_), toggle(toggle_) {} - - bool GetStatus() const override { - const bool button_state = joystick->GetButton(button); - if (!toggle) { - return button_state; - } - - if (button_state) { - return joystick->ToggleButton(button); - } - return joystick->UnlockButton(button); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int button; - bool toggle; -}; - -class SDLDirectionButton final : public Input::ButtonDevice { -public: - explicit SDLDirectionButton(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - bool GetStatus() const override { - return joystick->GetHatDirection(hat, direction); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisButton final : public Input::ButtonDevice { -public: - explicit SDLAxisButton(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - bool GetStatus() const override { - const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f); - if (trigger_if_greater) { - return axis_value > threshold; - } - return axis_value < threshold; - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLAnalog final : public Input::AnalogDevice { -public: - explicit SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, - bool invert_x_, bool invert_y_, float deadzone_, float range_, - float offset_x_, float offset_y_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), invert_x(invert_x_), - invert_y(invert_y_), deadzone(deadzone_), range(range_), offset_x(offset_x_), - offset_y(offset_y_) {} - - std::tuple<float, float> GetStatus() const override { - auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range, offset_x, offset_y); - const float r = std::sqrt((x * x) + (y * y)); - if (invert_x) { - x = -x; - } - if (invert_y) { - y = -y; - } - - if (r > deadzone) { - return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), - y / r * (r - deadzone) / (1 - deadzone)); - } - return {}; - } - - std::tuple<float, float> GetRawStatus() const override { - const float x = joystick->GetAxis(axis_x, range, offset_x); - const float y = joystick->GetAxis(axis_y, range, offset_y); - return {x, -y}; - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {deadzone, range, 0.5f}; - } - - bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { - const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.5f; - switch (direction) { - case Input::AnalogDirection::RIGHT: - return x > directional_deadzone; - case Input::AnalogDirection::LEFT: - return x < -directional_deadzone; - case Input::AnalogDirection::UP: - return y > directional_deadzone; - case Input::AnalogDirection::DOWN: - return y < -directional_deadzone; - } - return false; - } - -private: - std::shared_ptr<SDLJoystick> joystick; - const int axis_x; - const int axis_y; - const bool invert_x; - const bool invert_y; - const float deadzone; - const float range; - const float offset_x; - const float offset_y; -}; - -class SDLVibration final : public Input::VibrationDevice { -public: - explicit SDLVibration(std::shared_ptr<SDLJoystick> joystick_) - : joystick(std::move(joystick_)) {} - - u8 GetStatus() const override { - joystick->RumblePlay(1, 1); - return joystick->RumblePlay(0, 0); - } - - bool SetRumblePlay(f32 amp_low, [[maybe_unused]] f32 freq_low, f32 amp_high, - [[maybe_unused]] f32 freq_high) const override { - const auto process_amplitude = [](f32 amplitude) { - return static_cast<u16>((amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF); - }; - - const auto processed_amp_low = process_amplitude(amp_low); - const auto processed_amp_high = process_amplitude(amp_high); - - return joystick->RumblePlay(processed_amp_low, processed_amp_high); - } - -private: - std::shared_ptr<SDLJoystick> joystick; -}; - -class SDLMotion final : public Input::MotionDevice { -public: - explicit SDLMotion(std::shared_ptr<SDLJoystick> joystick_) : joystick(std::move(joystick_)) {} - - Input::MotionStatus GetStatus() const override { - return joystick->GetMotion().GetMotion(); - } - -private: - std::shared_ptr<SDLJoystick> joystick; -}; - -class SDLDirectionMotion final : public Input::MotionDevice { -public: - explicit SDLDirectionMotion(std::shared_ptr<SDLJoystick> joystick_, int hat_, Uint8 direction_) - : joystick(std::move(joystick_)), hat(hat_), direction(direction_) {} - - Input::MotionStatus GetStatus() const override { - if (joystick->GetHatDirection(hat, direction)) { - return joystick->GetMotion().GetRandomMotion(2, 6); - } - return joystick->GetMotion().GetRandomMotion(0, 0); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int hat; - Uint8 direction; -}; - -class SDLAxisMotion final : public Input::MotionDevice { -public: - explicit SDLAxisMotion(std::shared_ptr<SDLJoystick> joystick_, int axis_, float threshold_, - bool trigger_if_greater_) - : joystick(std::move(joystick_)), axis(axis_), threshold(threshold_), - trigger_if_greater(trigger_if_greater_) {} - - Input::MotionStatus GetStatus() const override { - const float axis_value = joystick->GetAxis(axis, 1.0f, 0.0f); - bool trigger = axis_value < threshold; - if (trigger_if_greater) { - trigger = axis_value > threshold; - } - - if (trigger) { - return joystick->GetMotion().GetRandomMotion(2, 6); - } - return joystick->GetMotion().GetRandomMotion(0, 0); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int axis; - float threshold; - bool trigger_if_greater; -}; - -class SDLButtonMotion final : public Input::MotionDevice { -public: - explicit SDLButtonMotion(std::shared_ptr<SDLJoystick> joystick_, int button_) - : joystick(std::move(joystick_)), button(button_) {} - - Input::MotionStatus GetStatus() const override { - if (joystick->GetButton(button)) { - return joystick->GetMotion().GetRandomMotion(2, 6); - } - return joystick->GetMotion().GetRandomMotion(0, 0); - } - -private: - std::shared_ptr<SDLJoystick> joystick; - int button; -}; - -/// A button device factory that creates button devices from SDL joystick -class SDLButtonFactory final : public Input::Factory<Input::ButtonDevice> { -public: - explicit SDLButtonFactory(SDLState& state_) : state(state_) {} - - /** - * Creates a button device from a joystick button - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type to bind - * - "button"(optional): the index of the button to bind - * - "hat"(optional): the index of the hat to bind as direction buttons - * - "axis"(optional): the index of the axis to bind - * - "direction"(only used for hat): the direction name of the hat to bind. Can be "up", - * "down", "left" or "right" - * - "threshold"(only used for axis): a float value in (-1.0, 1.0) which the button is - * triggered if the axis value crosses - * - "direction"(only used for axis): "+" means the button is triggered when the axis - * value is greater than the threshold; "-" means the button is triggered when the axis - * value is smaller than the threshold - */ - std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const auto toggle = params.Get("toggle", false); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique<SDLDirectionButton>(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - // Convert range from (0.0, 1.0) to (-1.0, 1.0) - const float threshold = (params.Get("threshold", 0.5f) - 0.5f) * 2.0f; - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->PreSetAxis(axis); - return std::make_unique<SDLAxisButton>(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->PreSetButton(button); - return std::make_unique<SDLButton>(joystick, button, toggle); - } - -private: - SDLState& state; -}; - -/// An analog device factory that creates analog devices from SDL joystick -class SDLAnalogFactory final : public Input::Factory<Input::AnalogDevice> { -public: - explicit SDLAnalogFactory(SDLState& state_) : state(state_) {} - /** - * Creates an analog device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ - std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - const int axis_x = params.Get("axis_x", 0); - const int axis_y = params.Get("axis_y", 1); - const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); - const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); - const std::string invert_x_value = params.Get("invert_x", "+"); - const std::string invert_y_value = params.Get("invert_y", "+"); - const bool invert_x = invert_x_value == "-"; - const bool invert_y = invert_y_value == "-"; - const float offset_x = std::clamp(params.Get("offset_x", 0.0f), -0.99f, 0.99f); - const float offset_y = std::clamp(params.Get("offset_y", 0.0f), -0.99f, 0.99f); - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - // This is necessary so accessing GetAxis with axis_x and axis_y won't crash - joystick->PreSetAxis(axis_x); - joystick->PreSetAxis(axis_y); - return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, invert_x, invert_y, deadzone, - range, offset_x, offset_y); - } - -private: - SDLState& state; -}; - -/// An vibration device factory that creates vibration devices from SDL joystick -class SDLVibrationFactory final : public Input::Factory<Input::VibrationDevice> { -public: - explicit SDLVibrationFactory(SDLState& state_) : state(state_) {} - /** - * Creates a vibration device from a joystick - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - */ - std::unique_ptr<Input::VibrationDevice> Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - return std::make_unique<SDLVibration>(state.GetSDLJoystickByGUID(guid, port)); - } - -private: - SDLState& state; -}; - -/// A motion device factory that creates motion devices from SDL joystick -class SDLMotionFactory final : public Input::Factory<Input::MotionDevice> { -public: - explicit SDLMotionFactory(SDLState& state_) : state(state_) {} - /** - * Creates motion device from joystick axes - * @param params contains parameters for creating the device: - * - "guid": the guid of the joystick to bind - * - "port": the nth joystick of the same type - */ - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { - const std::string guid = params.Get("guid", "0"); - const int port = params.Get("port", 0); - - auto joystick = state.GetSDLJoystickByGUID(guid, port); - - if (params.Has("motion")) { - return std::make_unique<SDLMotion>(joystick); - } - - if (params.Has("hat")) { - const int hat = params.Get("hat", 0); - const std::string direction_name = params.Get("direction", ""); - Uint8 direction; - if (direction_name == "up") { - direction = SDL_HAT_UP; - } else if (direction_name == "down") { - direction = SDL_HAT_DOWN; - } else if (direction_name == "left") { - direction = SDL_HAT_LEFT; - } else if (direction_name == "right") { - direction = SDL_HAT_RIGHT; - } else { - direction = 0; - } - // This is necessary so accessing GetHat with hat won't crash - joystick->SetHat(hat, SDL_HAT_CENTERED); - return std::make_unique<SDLDirectionMotion>(joystick, hat, direction); - } - - if (params.Has("axis")) { - const int axis = params.Get("axis", 0); - const float threshold = params.Get("threshold", 0.5f); - const std::string direction_name = params.Get("direction", ""); - bool trigger_if_greater; - if (direction_name == "+") { - trigger_if_greater = true; - } else if (direction_name == "-") { - trigger_if_greater = false; - } else { - trigger_if_greater = true; - LOG_ERROR(Input, "Unknown direction {}", direction_name); - } - // This is necessary so accessing GetAxis with axis won't crash - joystick->PreSetAxis(axis); - return std::make_unique<SDLAxisMotion>(joystick, axis, threshold, trigger_if_greater); - } - - const int button = params.Get("button", 0); - // This is necessary so accessing GetButton with button won't crash - joystick->PreSetButton(button); - return std::make_unique<SDLButtonMotion>(joystick, button); - } - -private: - SDLState& state; -}; - -SDLState::SDLState() { - using namespace Input; - button_factory = std::make_shared<SDLButtonFactory>(*this); - analog_factory = std::make_shared<SDLAnalogFactory>(*this); - vibration_factory = std::make_shared<SDLVibrationFactory>(*this); - motion_factory = std::make_shared<SDLMotionFactory>(*this); - RegisterFactory<ButtonDevice>("sdl", button_factory); - RegisterFactory<AnalogDevice>("sdl", analog_factory); - RegisterFactory<VibrationDevice>("sdl", vibration_factory); - RegisterFactory<MotionDevice>("sdl", motion_factory); - - if (!Settings::values.enable_raw_input) { - // Disable raw input. When enabled this setting causes SDL to die when a web applet opens - SDL_SetHint(SDL_HINT_JOYSTICK_RAWINPUT, "0"); - } - - // Enable HIDAPI rumble. This prevents SDL from disabling motion on PS4 and PS5 controllers - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); - - // Tell SDL2 to use the hidapi driver. This will allow joycons to be detected as a - // GameController and not a generic one - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); - - // Turn off Pro controller home led - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_HOME_LED, "0"); - - // If the frontend is going to manage the event loop, then we don't start one here - start_thread = SDL_WasInit(SDL_INIT_JOYSTICK) == 0; - if (start_thread && SDL_Init(SDL_INIT_JOYSTICK) < 0) { - LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); - return; - } - has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER) != 0; - if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { - LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); - } - - SDL_AddEventWatch(&SDLEventWatcher, this); - - initialized = true; - if (start_thread) { - poll_thread = std::thread([this] { - using namespace std::chrono_literals; - while (initialized) { - SDL_PumpEvents(); - std::this_thread::sleep_for(1ms); - } - }); - } - // Because the events for joystick connection happens before we have our event watcher added, we - // can just open all the joysticks right here - for (int i = 0; i < SDL_NumJoysticks(); ++i) { - InitJoystick(i); - } -} - -SDLState::~SDLState() { - using namespace Input; - UnregisterFactory<ButtonDevice>("sdl"); - UnregisterFactory<AnalogDevice>("sdl"); - UnregisterFactory<VibrationDevice>("sdl"); - UnregisterFactory<MotionDevice>("sdl"); - - CloseJoysticks(); - SDL_DelEventWatch(&SDLEventWatcher, this); - - initialized = false; - if (start_thread) { - poll_thread.join(); - SDL_QuitSubSystem(SDL_INIT_JOYSTICK); - } -} - -std::vector<Common::ParamPackage> SDLState::GetInputDevices() { - std::scoped_lock lock(joystick_map_mutex); - std::vector<Common::ParamPackage> devices; - std::unordered_map<int, std::shared_ptr<SDLJoystick>> joycon_pairs; - for (const auto& [key, value] : joystick_map) { - for (const auto& joystick : value) { - if (!joystick->GetSDLJoystick()) { - continue; - } - std::string name = - fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); - devices.emplace_back(Common::ParamPackage{ - {"class", "sdl"}, - {"display", std::move(name)}, - {"guid", joystick->GetGUID()}, - {"port", std::to_string(joystick->GetPort())}, - }); - if (joystick->IsJoyconLeft()) { - joycon_pairs.insert_or_assign(joystick->GetPort(), joystick); - } - } - } - - // Add dual controllers - for (const auto& [key, value] : joystick_map) { - for (const auto& joystick : value) { - if (joystick->IsJoyconRight()) { - if (!joycon_pairs.contains(joystick->GetPort())) { - continue; - } - const auto joystick2 = joycon_pairs.at(joystick->GetPort()); - - std::string name = - fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); - devices.emplace_back(Common::ParamPackage{ - {"class", "sdl"}, - {"display", std::move(name)}, - {"guid", joystick->GetGUID()}, - {"guid2", joystick2->GetGUID()}, - {"port", std::to_string(joystick->GetPort())}, - }); - } - } - } - return devices; -} - -namespace { -Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, - float value = 0.1f) { - Common::ParamPackage params({{"engine", "sdl"}}); - params.Set("port", port); - params.Set("guid", std::move(guid)); - params.Set("axis", axis); - params.Set("threshold", "0.5"); - if (value > 0) { - params.Set("direction", "+"); - } else { - params.Set("direction", "-"); - } - return params; -} - -Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, s32 button) { - Common::ParamPackage params({{"engine", "sdl"}}); - params.Set("port", port); - params.Set("guid", std::move(guid)); - params.Set("button", button); - params.Set("toggle", false); - return params; -} - -Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, s32 hat, s32 value) { - Common::ParamPackage params({{"engine", "sdl"}}); - - params.Set("port", port); - params.Set("guid", std::move(guid)); - params.Set("hat", hat); - switch (value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - return params; -} - -Common::ParamPackage BuildMotionParam(int port, std::string guid) { - Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}}); - params.Set("port", port); - params.Set("guid", std::move(guid)); - return params; -} - -Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { - switch (event.type) { - case SDL_JOYAXISMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { - return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast<s32>(event.jaxis.axis), - event.jaxis.value); - } - break; - } - case SDL_JOYBUTTONUP: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) { - return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast<s32>(event.jbutton.button)); - } - break; - } - case SDL_JOYHATMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) { - return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast<s32>(event.jhat.hat), - static_cast<s32>(event.jhat.value)); - } - break; - } - } - return {}; -} - -Common::ParamPackage SDLEventToMotionParamPackage(SDLState& state, const SDL_Event& event) { - switch (event.type) { - case SDL_JOYAXISMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { - return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast<s32>(event.jaxis.axis), - event.jaxis.value); - } - break; - } - case SDL_JOYBUTTONUP: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which)) { - return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast<s32>(event.jbutton.button)); - } - break; - } - case SDL_JOYHATMOTION: { - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which)) { - return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), - static_cast<s32>(event.jhat.hat), - static_cast<s32>(event.jhat.value)); - } - break; - } - case SDL_CONTROLLERSENSORUPDATE: { - bool is_motion_shaking = false; - constexpr float gyro_threshold = 5.0f; - constexpr float accel_threshold = 11.0f; - if (event.csensor.sensor == SDL_SENSOR_ACCEL) { - const Common::Vec3f acceleration = {-event.csensor.data[0], event.csensor.data[2], - -event.csensor.data[1]}; - if (acceleration.Length() > accel_threshold) { - is_motion_shaking = true; - } - } - - if (event.csensor.sensor == SDL_SENSOR_GYRO) { - const Common::Vec3f gyroscope = {event.csensor.data[0], -event.csensor.data[2], - event.csensor.data[1]}; - if (gyroscope.Length() > gyro_threshold) { - is_motion_shaking = true; - } - } - - if (!is_motion_shaking) { - break; - } - - if (const auto joystick = state.GetSDLJoystickBySDLID(event.csensor.which)) { - return BuildMotionParam(joystick->GetPort(), joystick->GetGUID()); - } - break; - } - } - return {}; -} - -Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid, - const SDL_GameControllerButtonBind& binding) { - switch (binding.bindType) { - case SDL_CONTROLLER_BINDTYPE_NONE: - break; - case SDL_CONTROLLER_BINDTYPE_AXIS: - return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); - case SDL_CONTROLLER_BINDTYPE_BUTTON: - return BuildButtonParamPackageForButton(port, guid, binding.value.button); - case SDL_CONTROLLER_BINDTYPE_HAT: - return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, - binding.value.hat.hat_mask); - } - return {}; -} - -Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, - int axis_y, float offset_x, float offset_y) { - Common::ParamPackage params; - params.Set("engine", "sdl"); - params.Set("port", port); - params.Set("guid", guid); - params.Set("axis_x", axis_x); - params.Set("axis_y", axis_y); - params.Set("offset_x", offset_x); - params.Set("offset_y", offset_y); - params.Set("invert_x", "+"); - params.Set("invert_y", "+"); - return params; -} -} // Anonymous namespace - -ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { - if (!params.Has("guid") || !params.Has("port")) { - return {}; - } - const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - - // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. - // We will add those afterwards - // This list also excludes Screenshot since theres not really a mapping for that - ButtonBindings switch_to_sdl_button; - - if (SDL_GameControllerGetType(controller) == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) { - switch_to_sdl_button = GetNintendoButtonBinding(joystick); - } else { - switch_to_sdl_button = GetDefaultButtonBinding(); - } - - // Add the missing bindings for ZL/ZR - static constexpr ZButtonBindings switch_to_sdl_axis{{ - {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, - {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, - }}; - - // Parameters contain two joysticks return dual - if (params.Has("guid2")) { - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - - if (joystick2->GetSDLGameController() != nullptr) { - return GetDualControllerMapping(joystick, joystick2, switch_to_sdl_button, - switch_to_sdl_axis); - } - } - - return GetSingleControllerMapping(joystick, switch_to_sdl_button, switch_to_sdl_axis); -} - -ButtonBindings SDLState::GetDefaultButtonBinding() const { - return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - {Settings::NativeButton::SL, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::SR, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - }; -} - -ButtonBindings SDLState::GetNintendoButtonBinding( - const std::shared_ptr<SDLJoystick>& joystick) const { - // Default SL/SR mapping for pro controllers - auto sl_button = SDL_CONTROLLER_BUTTON_LEFTSHOULDER; - auto sr_button = SDL_CONTROLLER_BUTTON_RIGHTSHOULDER; - - if (joystick->IsJoyconLeft()) { - sl_button = SDL_CONTROLLER_BUTTON_PADDLE2; - sr_button = SDL_CONTROLLER_BUTTON_PADDLE4; - } - if (joystick->IsJoyconRight()) { - sl_button = SDL_CONTROLLER_BUTTON_PADDLE3; - sr_button = SDL_CONTROLLER_BUTTON_PADDLE1; - } - - return { - std::pair{Settings::NativeButton::A, SDL_CONTROLLER_BUTTON_A}, - {Settings::NativeButton::B, SDL_CONTROLLER_BUTTON_B}, - {Settings::NativeButton::X, SDL_CONTROLLER_BUTTON_X}, - {Settings::NativeButton::Y, SDL_CONTROLLER_BUTTON_Y}, - {Settings::NativeButton::LStick, SDL_CONTROLLER_BUTTON_LEFTSTICK}, - {Settings::NativeButton::RStick, SDL_CONTROLLER_BUTTON_RIGHTSTICK}, - {Settings::NativeButton::L, SDL_CONTROLLER_BUTTON_LEFTSHOULDER}, - {Settings::NativeButton::R, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER}, - {Settings::NativeButton::Plus, SDL_CONTROLLER_BUTTON_START}, - {Settings::NativeButton::Minus, SDL_CONTROLLER_BUTTON_BACK}, - {Settings::NativeButton::DLeft, SDL_CONTROLLER_BUTTON_DPAD_LEFT}, - {Settings::NativeButton::DUp, SDL_CONTROLLER_BUTTON_DPAD_UP}, - {Settings::NativeButton::DRight, SDL_CONTROLLER_BUTTON_DPAD_RIGHT}, - {Settings::NativeButton::DDown, SDL_CONTROLLER_BUTTON_DPAD_DOWN}, - {Settings::NativeButton::SL, sl_button}, - {Settings::NativeButton::SR, sr_button}, - {Settings::NativeButton::Home, SDL_CONTROLLER_BUTTON_GUIDE}, - }; -} - -ButtonMapping SDLState::GetSingleControllerMapping( - const std::shared_ptr<SDLJoystick>& joystick, const ButtonBindings& switch_to_sdl_button, - const ZButtonBindings& switch_to_sdl_axis) const { - ButtonMapping mapping; - mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); - auto* controller = joystick->GetSDLGameController(); - - for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - - return mapping; -} - -ButtonMapping SDLState::GetDualControllerMapping(const std::shared_ptr<SDLJoystick>& joystick, - const std::shared_ptr<SDLJoystick>& joystick2, - const ButtonBindings& switch_to_sdl_button, - const ZButtonBindings& switch_to_sdl_axis) const { - ButtonMapping mapping; - mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); - auto* controller = joystick->GetSDLGameController(); - auto* controller2 = joystick2->GetSDLGameController(); - - for (const auto& [switch_button, sdl_button] : switch_to_sdl_button) { - if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForButton(controller2, sdl_button); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); - continue; - } - const auto& binding = SDL_GameControllerGetBindForButton(controller, sdl_button); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - for (const auto& [switch_button, sdl_axis] : switch_to_sdl_axis) { - if (IsButtonOnLeftSide(switch_button)) { - const auto& binding = SDL_GameControllerGetBindForAxis(controller2, sdl_axis); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick2->GetPort(), joystick2->GetGUID(), binding)); - continue; - } - const auto& binding = SDL_GameControllerGetBindForAxis(controller, sdl_axis); - mapping.insert_or_assign( - switch_button, - BuildParamPackageForBinding(joystick->GetPort(), joystick->GetGUID(), binding)); - } - - return mapping; -} - -bool SDLState::IsButtonOnLeftSide(Settings::NativeButton::Values button) const { - switch (button) { - case Settings::NativeButton::DDown: - case Settings::NativeButton::DLeft: - case Settings::NativeButton::DRight: - case Settings::NativeButton::DUp: - case Settings::NativeButton::L: - case Settings::NativeButton::LStick: - case Settings::NativeButton::Minus: - case Settings::NativeButton::Screenshot: - case Settings::NativeButton::ZL: - return true; - default: - return false; - } -} - -AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) { - if (!params.Has("guid") || !params.Has("port")) { - return {}; - } - const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - - AnalogMapping mapping = {}; - const auto& binding_left_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX); - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY); - if (params.Has("guid2")) { - joystick2->PreSetAxis(binding_left_x.value.axis); - joystick2->PreSetAxis(binding_left_y.value.axis); - const auto left_offset_x = -joystick2->GetAxis(binding_left_x.value.axis, 1.0f, 0); - const auto left_offset_y = -joystick2->GetAxis(binding_left_y.value.axis, 1.0f, 0); - mapping.insert_or_assign( - Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(joystick2->GetPort(), joystick2->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis, - left_offset_x, left_offset_y)); - } else { - joystick->PreSetAxis(binding_left_x.value.axis); - joystick->PreSetAxis(binding_left_y.value.axis); - const auto left_offset_x = -joystick->GetAxis(binding_left_x.value.axis, 1.0f, 0); - const auto left_offset_y = -joystick->GetAxis(binding_left_y.value.axis, 1.0f, 0); - mapping.insert_or_assign( - Settings::NativeAnalog::LStick, - BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_left_x.value.axis, binding_left_y.value.axis, - left_offset_x, left_offset_y)); - } - const auto& binding_right_x = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); - joystick->PreSetAxis(binding_right_x.value.axis); - joystick->PreSetAxis(binding_right_y.value.axis); - const auto right_offset_x = -joystick->GetAxis(binding_right_x.value.axis, 1.0f, 0); - const auto right_offset_y = -joystick->GetAxis(binding_right_y.value.axis, 1.0f, 0); - mapping.insert_or_assign(Settings::NativeAnalog::RStick, - BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - binding_right_x.value.axis, - binding_right_y.value.axis, right_offset_x, - right_offset_y)); - return mapping; -} - -MotionMapping SDLState::GetMotionMappingForDevice(const Common::ParamPackage& params) { - if (!params.Has("guid") || !params.Has("port")) { - return {}; - } - const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); - const auto joystick2 = GetSDLJoystickByGUID(params.Get("guid2", ""), params.Get("port", 0)); - auto* controller = joystick->GetSDLGameController(); - if (controller == nullptr) { - return {}; - } - - MotionMapping mapping = {}; - joystick->EnableMotion(); - - if (joystick->HasGyro() || joystick->HasAccel()) { - mapping.insert_or_assign(Settings::NativeMotion::MotionRight, - BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); - } - if (params.Has("guid2")) { - joystick2->EnableMotion(); - if (joystick2->HasGyro() || joystick2->HasAccel()) { - mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, - BuildMotionParam(joystick2->GetPort(), joystick2->GetGUID())); - } - } else { - if (joystick->HasGyro() || joystick->HasAccel()) { - mapping.insert_or_assign(Settings::NativeMotion::MotionLeft, - BuildMotionParam(joystick->GetPort(), joystick->GetGUID())); - } - } - - return mapping; -} -namespace Polling { -class SDLPoller : public InputCommon::Polling::DevicePoller { -public: - explicit SDLPoller(SDLState& state_) : state(state_) {} - - void Start([[maybe_unused]] const std::string& device_id) override { - state.event_queue.Clear(); - state.polling = true; - } - - void Stop() override { - state.polling = false; - } - -protected: - SDLState& state; -}; - -class SDLButtonPoller final : public SDLPoller { -public: - explicit SDLButtonPoller(SDLState& state_) : SDLPoller(state_) {} - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - const auto package = FromEvent(event); - if (package) { - return *package; - } - } - return {}; - } - [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(SDL_Event& event) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (!axis_memory.count(event.jaxis.which) || - !axis_memory[event.jaxis.which].count(event.jaxis.axis)) { - axis_memory[event.jaxis.which][event.jaxis.axis] = event.jaxis.value; - axis_event_count[event.jaxis.which][event.jaxis.axis] = 1; - break; - } else { - axis_event_count[event.jaxis.which][event.jaxis.axis]++; - // The joystick and axis exist in our map if we take this branch, so no checks - // needed - if (std::abs( - (event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis]) / - 32767.0) < 0.5) { - break; - } else { - if (axis_event_count[event.jaxis.which][event.jaxis.axis] == 2 && - IsAxisAtPole(event.jaxis.value) && - IsAxisAtPole(axis_memory[event.jaxis.which][event.jaxis.axis])) { - // If we have exactly two events and both are near a pole, this is - // likely a digital input masquerading as an analog axis; Instead of - // trying to look at the direction the axis travelled, assume the first - // event was press and the second was release; This should handle most - // digital axes while deferring to the direction of travel for analog - // axes - event.jaxis.value = static_cast<Sint16>( - std::copysign(32767, axis_memory[event.jaxis.which][event.jaxis.axis])); - } else { - // There are more than two events, so this is likely a true analog axis, - // check the direction it travelled - event.jaxis.value = static_cast<Sint16>(std::copysign( - 32767, - event.jaxis.value - axis_memory[event.jaxis.which][event.jaxis.axis])); - } - axis_memory.clear(); - axis_event_count.clear(); - } - } - [[fallthrough]]; - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return {SDLEventToButtonParamPackage(state, event)}; - } - return std::nullopt; - } - -private: - // Determine whether an axis value is close to an extreme or center - // Some controllers have a digital D-Pad as a pair of analog sticks, with 3 possible values per - // axis, which is why the center must be considered a pole - bool IsAxisAtPole(int16_t value) const { - return std::abs(value) >= 32767 || std::abs(value) < 327; - } - std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, int16_t>> axis_memory; - std::unordered_map<SDL_JoystickID, std::unordered_map<uint8_t, uint32_t>> axis_event_count; -}; - -class SDLMotionPoller final : public SDLPoller { -public: - explicit SDLMotionPoller(SDLState& state_) : SDLPoller(state_) {} - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - const auto package = FromEvent(event); - if (package) { - return *package; - } - } - return {}; - } - [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - break; - } - [[fallthrough]]; - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - case SDL_CONTROLLERSENSORUPDATE: - return {SDLEventToMotionParamPackage(state, event)}; - } - return std::nullopt; - } -}; - -/** - * Attempts to match the press to a controller joy axis (left/right stick) and if a match - * isn't found, checks if the event matches anything from SDLButtonPoller and uses that - * instead - */ -class SDLAnalogPreferredPoller final : public SDLPoller { -public: - explicit SDLAnalogPreferredPoller(SDLState& state_) - : SDLPoller(state_), button_poller(state_) {} - - void Start(const std::string& device_id) override { - SDLPoller::Start(device_id); - // Reset stored axes - first_axis = -1; - } - - Common::ParamPackage GetNextInput() override { - SDL_Event event; - while (state.event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION) { - // Check for a button press - auto button_press = button_poller.FromEvent(event); - if (button_press) { - return *button_press; - } - continue; - } - const auto axis = event.jaxis.axis; - - // Filter out axis events that are below a threshold - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - continue; - } - - // Filter out axis events that are the same - if (first_axis == axis) { - continue; - } - - // In order to return a complete analog param, we need inputs for both axes. - // If the first axis isn't set we set the value then wait till next event - if (first_axis == -1) { - first_axis = axis; - continue; - } - - if (const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which)) { - // Set offset to zero since the joystick is not on center - auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), - first_axis, axis, 0, 0); - first_axis = -1; - return params; - } - } - return {}; - } - -private: - int first_axis = -1; - SDLButtonPoller button_poller; -}; -} // namespace Polling - -SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { - Pollers pollers; - - switch (type) { - case InputCommon::Polling::DeviceType::AnalogPreferred: - pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this)); - break; - case InputCommon::Polling::DeviceType::Button: - pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); - break; - case InputCommon::Polling::DeviceType::Motion: - pollers.emplace_back(std::make_unique<Polling::SDLMotionPoller>(*this)); - break; - } - - return pollers; -} - -} // namespace InputCommon::SDL diff --git a/src/input_common/tas/tas_input.cpp b/src/input_common/tas/tas_input.cpp deleted file mode 100644 index 1598092b6..000000000 --- a/src/input_common/tas/tas_input.cpp +++ /dev/null @@ -1,455 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2+ -// Refer to the license.txt file included. - -#include <cstring> -#include <regex> - -#include "common/fs/file.h" -#include "common/fs/fs_types.h" -#include "common/fs/path_util.h" -#include "common/logging/log.h" -#include "common/settings.h" -#include "input_common/tas/tas_input.h" - -namespace TasInput { - -// Supported keywords and buttons from a TAS file -constexpr std::array<std::pair<std::string_view, TasButton>, 20> text_to_tas_button = { - std::pair{"KEY_A", TasButton::BUTTON_A}, - {"KEY_B", TasButton::BUTTON_B}, - {"KEY_X", TasButton::BUTTON_X}, - {"KEY_Y", TasButton::BUTTON_Y}, - {"KEY_LSTICK", TasButton::STICK_L}, - {"KEY_RSTICK", TasButton::STICK_R}, - {"KEY_L", TasButton::TRIGGER_L}, - {"KEY_R", TasButton::TRIGGER_R}, - {"KEY_PLUS", TasButton::BUTTON_PLUS}, - {"KEY_MINUS", TasButton::BUTTON_MINUS}, - {"KEY_DLEFT", TasButton::BUTTON_LEFT}, - {"KEY_DUP", TasButton::BUTTON_UP}, - {"KEY_DRIGHT", TasButton::BUTTON_RIGHT}, - {"KEY_DDOWN", TasButton::BUTTON_DOWN}, - {"KEY_SL", TasButton::BUTTON_SL}, - {"KEY_SR", TasButton::BUTTON_SR}, - {"KEY_CAPTURE", TasButton::BUTTON_CAPTURE}, - {"KEY_HOME", TasButton::BUTTON_HOME}, - {"KEY_ZL", TasButton::TRIGGER_ZL}, - {"KEY_ZR", TasButton::TRIGGER_ZR}, -}; - -Tas::Tas() { - if (!Settings::values.tas_enable) { - needs_reset = true; - return; - } - LoadTasFiles(); -} - -Tas::~Tas() { - Stop(); -}; - -void Tas::LoadTasFiles() { - script_length = 0; - for (size_t i = 0; i < commands.size(); i++) { - LoadTasFile(i); - if (commands[i].size() > script_length) { - script_length = commands[i].size(); - } - } -} - -void Tas::LoadTasFile(size_t player_index) { - if (!commands[player_index].empty()) { - commands[player_index].clear(); - } - std::string file = - Common::FS::ReadStringFromFile(Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / - fmt::format("script0-{}.txt", player_index + 1), - Common::FS::FileType::BinaryFile); - std::stringstream command_line(file); - std::string line; - int frame_no = 0; - while (std::getline(command_line, line, '\n')) { - if (line.empty()) { - continue; - } - LOG_DEBUG(Input, "Loading line: {}", line); - std::smatch m; - - std::stringstream linestream(line); - std::string segment; - std::vector<std::string> seglist; - - while (std::getline(linestream, segment, ' ')) { - seglist.push_back(segment); - } - - if (seglist.size() < 4) { - continue; - } - - while (frame_no < std::stoi(seglist.at(0))) { - commands[player_index].push_back({}); - frame_no++; - } - - TASCommand command = { - .buttons = ReadCommandButtons(seglist.at(1)), - .l_axis = ReadCommandAxis(seglist.at(2)), - .r_axis = ReadCommandAxis(seglist.at(3)), - }; - commands[player_index].push_back(command); - frame_no++; - } - LOG_INFO(Input, "TAS file loaded! {} frames", frame_no); -} - -void Tas::WriteTasFile(std::u8string file_name) { - std::string output_text; - for (size_t frame = 0; frame < record_commands.size(); frame++) { - if (!output_text.empty()) { - output_text += "\n"; - } - const TASCommand& line = record_commands[frame]; - output_text += std::to_string(frame) + " " + WriteCommandButtons(line.buttons) + " " + - WriteCommandAxis(line.l_axis) + " " + WriteCommandAxis(line.r_axis); - } - const auto bytes_written = Common::FS::WriteStringToFile( - Common::FS::GetYuzuPath(Common::FS::YuzuPath::TASDir) / file_name, - Common::FS::FileType::TextFile, output_text); - if (bytes_written == output_text.size()) { - LOG_INFO(Input, "TAS file written to file!"); - } else { - LOG_ERROR(Input, "Writing the TAS-file has failed! {} / {} bytes written", bytes_written, - output_text.size()); - } -} - -std::pair<float, float> Tas::FlipAxisY(std::pair<float, float> old) { - auto [x, y] = old; - return {x, -y}; -} - -void Tas::RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes) { - last_input = {buttons, FlipAxisY(axes[0]), FlipAxisY(axes[1])}; -} - -std::tuple<TasState, size_t, size_t> Tas::GetStatus() const { - TasState state; - if (is_recording) { - return {TasState::Recording, 0, record_commands.size()}; - } - - if (is_running) { - state = TasState::Running; - } else { - state = TasState::Stopped; - } - - return {state, current_command, script_length}; -} - -std::string Tas::DebugButtons(u32 buttons) const { - return fmt::format("{{ {} }}", TasInput::Tas::ButtonsToString(buttons)); -} - -std::string Tas::DebugJoystick(float x, float y) const { - return fmt::format("[ {} , {} ]", std::to_string(x), std::to_string(y)); -} - -std::string Tas::DebugInput(const TasData& data) const { - return fmt::format("{{ {} , {} , {} }}", DebugButtons(data.buttons), - DebugJoystick(data.axis[0], data.axis[1]), - DebugJoystick(data.axis[2], data.axis[3])); -} - -std::string Tas::DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const { - std::string returns = "[ "; - for (size_t i = 0; i < arr.size(); i++) { - returns += DebugInput(arr[i]); - if (i != arr.size() - 1) { - returns += " , "; - } - } - return returns + "]"; -} - -std::string Tas::ButtonsToString(u32 button) const { - std::string returns; - for (auto [text_button, tas_button] : text_to_tas_button) { - if ((button & static_cast<u32>(tas_button)) != 0) - returns += fmt::format(", {}", text_button.substr(4)); - } - return returns.empty() ? "" : returns.substr(2); -} - -void Tas::UpdateThread() { - if (!Settings::values.tas_enable) { - if (is_running) { - Stop(); - } - return; - } - - if (is_recording) { - record_commands.push_back(last_input); - } - if (needs_reset) { - current_command = 0; - needs_reset = false; - LoadTasFiles(); - LOG_DEBUG(Input, "tas_reset done"); - } - - if (!is_running) { - tas_data.fill({}); - return; - } - if (current_command < script_length) { - LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; - for (size_t i = 0; i < commands.size(); i++) { - if (frame < commands[i].size()) { - TASCommand command = commands[i][frame]; - tas_data[i].buttons = command.buttons; - auto [l_axis_x, l_axis_y] = command.l_axis; - tas_data[i].axis[0] = l_axis_x; - tas_data[i].axis[1] = l_axis_y; - auto [r_axis_x, r_axis_y] = command.r_axis; - tas_data[i].axis[2] = r_axis_x; - tas_data[i].axis[3] = r_axis_y; - } else { - tas_data[i] = {}; - } - } - } else { - is_running = Settings::values.tas_loop.GetValue(); - current_command = 0; - tas_data.fill({}); - if (!is_running) { - SwapToStoredController(); - } - } - LOG_DEBUG(Input, "TAS inputs: {}", DebugInputs(tas_data)); -} - -TasAnalog Tas::ReadCommandAxis(const std::string& line) const { - std::stringstream linestream(line); - std::string segment; - std::vector<std::string> seglist; - - while (std::getline(linestream, segment, ';')) { - seglist.push_back(segment); - } - - const float x = std::stof(seglist.at(0)) / 32767.0f; - const float y = std::stof(seglist.at(1)) / 32767.0f; - - return {x, y}; -} - -u32 Tas::ReadCommandButtons(const std::string& data) const { - std::stringstream button_text(data); - std::string line; - u32 buttons = 0; - while (std::getline(button_text, line, ';')) { - for (auto [text, tas_button] : text_to_tas_button) { - if (text == line) { - buttons |= static_cast<u32>(tas_button); - break; - } - } - } - return buttons; -} - -std::string Tas::WriteCommandAxis(TasAnalog data) const { - auto [x, y] = data; - std::string line; - line += std::to_string(static_cast<int>(x * 32767)); - line += ";"; - line += std::to_string(static_cast<int>(y * 32767)); - return line; -} - -std::string Tas::WriteCommandButtons(u32 data) const { - if (data == 0) { - return "NONE"; - } - - std::string line; - u32 index = 0; - while (data > 0) { - if ((data & 1) == 1) { - for (auto [text, tas_button] : text_to_tas_button) { - if (tas_button == static_cast<TasButton>(1 << index)) { - if (line.size() > 0) { - line += ";"; - } - line += text; - break; - } - } - } - index++; - data >>= 1; - } - return line; -} - -void Tas::StartStop() { - if (!Settings::values.tas_enable) { - return; - } - if (is_running) { - Stop(); - } else { - is_running = true; - SwapToTasController(); - } -} - -void Tas::Stop() { - is_running = false; - SwapToStoredController(); -} - -void Tas::SwapToTasController() { - if (!Settings::values.tas_swap_controllers) { - return; - } - auto& players = Settings::values.players.GetValue(); - for (std::size_t index = 0; index < players.size(); index++) { - auto& player = players[index]; - player_mappings[index] = player; - - // Only swap active controllers - if (!player.connected) { - continue; - } - - Common::ParamPackage tas_param; - tas_param.Set("pad", static_cast<u8>(index)); - auto button_mapping = GetButtonMappingForDevice(tas_param); - auto analog_mapping = GetAnalogMappingForDevice(tas_param); - auto& buttons = player.buttons; - auto& analogs = player.analogs; - - for (std::size_t i = 0; i < buttons.size(); ++i) { - buttons[i] = button_mapping[static_cast<Settings::NativeButton::Values>(i)].Serialize(); - } - for (std::size_t i = 0; i < analogs.size(); ++i) { - analogs[i] = analog_mapping[static_cast<Settings::NativeAnalog::Values>(i)].Serialize(); - } - } - is_old_input_saved = true; - Settings::values.is_device_reload_pending.store(true); -} - -void Tas::SwapToStoredController() { - if (!is_old_input_saved) { - return; - } - auto& players = Settings::values.players.GetValue(); - for (std::size_t index = 0; index < players.size(); index++) { - players[index] = player_mappings[index]; - } - is_old_input_saved = false; - Settings::values.is_device_reload_pending.store(true); -} - -void Tas::Reset() { - if (!Settings::values.tas_enable) { - return; - } - needs_reset = true; -} - -bool Tas::Record() { - if (!Settings::values.tas_enable) { - return true; - } - is_recording = !is_recording; - return is_recording; -} - -void Tas::SaveRecording(bool overwrite_file) { - if (is_recording) { - return; - } - if (record_commands.empty()) { - return; - } - WriteTasFile(u8"record.txt"); - if (overwrite_file) { - WriteTasFile(u8"script0-1.txt"); - } - needs_reset = true; - record_commands.clear(); -} - -InputCommon::ButtonMapping Tas::GetButtonMappingForDevice( - const Common::ParamPackage& params) const { - // This list is missing ZL/ZR since those are not considered buttons. - // We will add those afterwards - // This list also excludes any button that can't be really mapped - static constexpr std::array<std::pair<Settings::NativeButton::Values, TasButton>, 20> - switch_to_tas_button = { - std::pair{Settings::NativeButton::A, TasButton::BUTTON_A}, - {Settings::NativeButton::B, TasButton::BUTTON_B}, - {Settings::NativeButton::X, TasButton::BUTTON_X}, - {Settings::NativeButton::Y, TasButton::BUTTON_Y}, - {Settings::NativeButton::LStick, TasButton::STICK_L}, - {Settings::NativeButton::RStick, TasButton::STICK_R}, - {Settings::NativeButton::L, TasButton::TRIGGER_L}, - {Settings::NativeButton::R, TasButton::TRIGGER_R}, - {Settings::NativeButton::Plus, TasButton::BUTTON_PLUS}, - {Settings::NativeButton::Minus, TasButton::BUTTON_MINUS}, - {Settings::NativeButton::DLeft, TasButton::BUTTON_LEFT}, - {Settings::NativeButton::DUp, TasButton::BUTTON_UP}, - {Settings::NativeButton::DRight, TasButton::BUTTON_RIGHT}, - {Settings::NativeButton::DDown, TasButton::BUTTON_DOWN}, - {Settings::NativeButton::SL, TasButton::BUTTON_SL}, - {Settings::NativeButton::SR, TasButton::BUTTON_SR}, - {Settings::NativeButton::Screenshot, TasButton::BUTTON_CAPTURE}, - {Settings::NativeButton::Home, TasButton::BUTTON_HOME}, - {Settings::NativeButton::ZL, TasButton::TRIGGER_ZL}, - {Settings::NativeButton::ZR, TasButton::TRIGGER_ZR}, - }; - - InputCommon::ButtonMapping mapping{}; - for (const auto& [switch_button, tas_button] : switch_to_tas_button) { - Common::ParamPackage button_params({{"engine", "tas"}}); - button_params.Set("pad", params.Get("pad", 0)); - button_params.Set("button", static_cast<int>(tas_button)); - mapping.insert_or_assign(switch_button, std::move(button_params)); - } - - return mapping; -} - -InputCommon::AnalogMapping Tas::GetAnalogMappingForDevice( - const Common::ParamPackage& params) const { - - InputCommon::AnalogMapping mapping = {}; - Common::ParamPackage left_analog_params; - left_analog_params.Set("engine", "tas"); - left_analog_params.Set("pad", params.Get("pad", 0)); - left_analog_params.Set("axis_x", static_cast<int>(TasAxes::StickX)); - left_analog_params.Set("axis_y", static_cast<int>(TasAxes::StickY)); - mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); - Common::ParamPackage right_analog_params; - right_analog_params.Set("engine", "tas"); - right_analog_params.Set("pad", params.Get("pad", 0)); - right_analog_params.Set("axis_x", static_cast<int>(TasAxes::SubstickX)); - right_analog_params.Set("axis_y", static_cast<int>(TasAxes::SubstickY)); - mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); - return mapping; -} - -const TasData& Tas::GetTasState(std::size_t pad) const { - return tas_data[pad]; -} -} // namespace TasInput diff --git a/src/input_common/tas/tas_input.h b/src/input_common/tas/tas_input.h deleted file mode 100644 index 3e2db8f00..000000000 --- a/src/input_common/tas/tas_input.h +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <array> - -#include "common/common_types.h" -#include "common/settings_input.h" -#include "core/frontend/input.h" -#include "input_common/main.h" - -/* -To play back TAS scripts on Yuzu, select the folder with scripts in the configuration menu below -Tools -> Configure TAS. The file itself has normal text format and has to be called script0-1.txt -for controller 1, script0-2.txt for controller 2 and so forth (with max. 8 players). - -A script file has the same format as TAS-nx uses, so final files will look like this: - -1 KEY_B 0;0 0;0 -6 KEY_ZL 0;0 0;0 -41 KEY_ZL;KEY_Y 0;0 0;0 -43 KEY_X;KEY_A 32767;0 0;0 -44 KEY_A 32767;0 0;0 -45 KEY_A 32767;0 0;0 -46 KEY_A 32767;0 0;0 -47 KEY_A 32767;0 0;0 - -After placing the file at the correct location, it can be read into Yuzu with the (default) hotkey -CTRL+F6 (refresh). In the bottom left corner, it will display the amount of frames the script file -has. Playback can be started or stopped using CTRL+F5. - -However, for playback to actually work, the correct input device has to be selected: In the Controls -menu, select TAS from the device list for the controller that the script should be played on. - -Recording a new script file is really simple: Just make sure that the proper device (not TAS) is -connected on P1, and press CTRL+F7 to start recording. When done, just press the same keystroke -again (CTRL+F7). The new script will be saved at the location previously selected, as the filename -record.txt. - -For debugging purposes, the common controller debugger can be used (View -> Debugging -> Controller -P1). -*/ - -namespace TasInput { - -constexpr size_t PLAYER_NUMBER = 8; - -using TasAnalog = std::pair<float, float>; - -enum class TasState { - Running, - Recording, - Stopped, -}; - -enum class TasButton : u32 { - BUTTON_A = 1U << 0, - BUTTON_B = 1U << 1, - BUTTON_X = 1U << 2, - BUTTON_Y = 1U << 3, - STICK_L = 1U << 4, - STICK_R = 1U << 5, - TRIGGER_L = 1U << 6, - TRIGGER_R = 1U << 7, - TRIGGER_ZL = 1U << 8, - TRIGGER_ZR = 1U << 9, - BUTTON_PLUS = 1U << 10, - BUTTON_MINUS = 1U << 11, - BUTTON_LEFT = 1U << 12, - BUTTON_UP = 1U << 13, - BUTTON_RIGHT = 1U << 14, - BUTTON_DOWN = 1U << 15, - BUTTON_SL = 1U << 16, - BUTTON_SR = 1U << 17, - BUTTON_HOME = 1U << 18, - BUTTON_CAPTURE = 1U << 19, -}; - -enum class TasAxes : u8 { - StickX, - StickY, - SubstickX, - SubstickY, - Undefined, -}; - -struct TasData { - u32 buttons{}; - std::array<float, 4> axis{}; -}; - -class Tas { -public: - Tas(); - ~Tas(); - - // Changes the input status that will be stored in each frame - void RecordInput(u32 buttons, const std::array<std::pair<float, float>, 2>& axes); - - // Main loop that records or executes input - void UpdateThread(); - - // Sets the flag to start or stop the TAS command excecution and swaps controllers profiles - void StartStop(); - - // Stop the TAS and reverts any controller profile - void Stop(); - - // Sets the flag to reload the file and start from the begining in the next update - void Reset(); - - /** - * Sets the flag to enable or disable recording of inputs - * @return Returns true if the current recording status is enabled - */ - bool Record(); - - // Saves contents of record_commands on a file if overwrite is enabled player 1 will be - // overwritten with the recorded commands - void SaveRecording(bool overwrite_file); - - /** - * Returns the current status values of TAS playback/recording - * @return Tuple of - * TasState indicating the current state out of Running, Recording or Stopped ; - * Current playback progress or amount of frames (so far) for Recording ; - * Total length of script file currently loaded or amount of frames (so far) for Recording - */ - std::tuple<TasState, size_t, size_t> GetStatus() const; - - // Retuns an array of the default button mappings - InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; - - // Retuns an array of the default analog mappings - InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; - [[nodiscard]] const TasData& GetTasState(std::size_t pad) const; - -private: - struct TASCommand { - u32 buttons{}; - TasAnalog l_axis{}; - TasAnalog r_axis{}; - }; - - // Loads TAS files from all players - void LoadTasFiles(); - - // Loads TAS file from the specified player - void LoadTasFile(size_t player_index); - - // Writes a TAS file from the recorded commands - void WriteTasFile(std::u8string file_name); - - /** - * Parses a string containing the axis values with the following format "x;y" - * X and Y have a range from -32767 to 32767 - * @return Returns a TAS analog object with axis values with range from -1.0 to 1.0 - */ - TasAnalog ReadCommandAxis(const std::string& line) const; - - /** - * Parses a string containing the button values with the following format "a;b;c;d..." - * Each button is represented by it's text format specified in text_to_tas_button array - * @return Returns a u32 with each bit representing the status of a button - */ - u32 ReadCommandButtons(const std::string& line) const; - - /** - * Converts an u32 containing the button status into the text equivalent - * @return Returns a string with the name of the buttons to be written to the file - */ - std::string WriteCommandButtons(u32 data) const; - - /** - * Converts an TAS analog object containing the axis status into the text equivalent - * @return Returns a string with the value of the axis to be written to the file - */ - std::string WriteCommandAxis(TasAnalog data) const; - - // Inverts the Y axis polarity - std::pair<float, float> FlipAxisY(std::pair<float, float> old); - - /** - * Converts an u32 containing the button status into the text equivalent - * @return Returns a string with the name of the buttons to be printed on console - */ - std::string DebugButtons(u32 buttons) const; - - /** - * Converts an TAS analog object containing the axis status into the text equivalent - * @return Returns a string with the value of the axis to be printed on console - */ - std::string DebugJoystick(float x, float y) const; - - /** - * Converts the given TAS status into the text equivalent - * @return Returns a string with the value of the TAS status to be printed on console - */ - std::string DebugInput(const TasData& data) const; - - /** - * Converts the given TAS status of multiple players into the text equivalent - * @return Returns a string with the value of the status of all TAS players to be printed on - * console - */ - std::string DebugInputs(const std::array<TasData, PLAYER_NUMBER>& arr) const; - - /** - * Converts an u32 containing the button status into the text equivalent - * @return Returns a string with the name of the buttons - */ - std::string ButtonsToString(u32 button) const; - - // Stores current controller configuration and sets a TAS controller for every active controller - // to the current config - void SwapToTasController(); - - // Sets the stored controller configuration to the current config - void SwapToStoredController(); - - size_t script_length{0}; - std::array<TasData, PLAYER_NUMBER> tas_data; - bool is_old_input_saved{false}; - bool is_recording{false}; - bool is_running{false}; - bool needs_reset{false}; - std::array<std::vector<TASCommand>, PLAYER_NUMBER> commands{}; - std::vector<TASCommand> record_commands{}; - size_t current_command{0}; - TASCommand last_input{}; // only used for recording - - // Old settings for swapping controllers - std::array<Settings::PlayerInput, 10> player_mappings; -}; -} // namespace TasInput diff --git a/src/input_common/tas/tas_poller.cpp b/src/input_common/tas/tas_poller.cpp deleted file mode 100644 index 15810d6b0..000000000 --- a/src/input_common/tas/tas_poller.cpp +++ /dev/null @@ -1,101 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <mutex> -#include <utility> - -#include "common/settings.h" -#include "common/threadsafe_queue.h" -#include "input_common/tas/tas_input.h" -#include "input_common/tas/tas_poller.h" - -namespace InputCommon { - -class TasButton final : public Input::ButtonDevice { -public: - explicit TasButton(u32 button_, u32 pad_, const TasInput::Tas* tas_input_) - : button(button_), pad(pad_), tas_input(tas_input_) {} - - bool GetStatus() const override { - return (tas_input->GetTasState(pad).buttons & button) != 0; - } - -private: - const u32 button; - const u32 pad; - const TasInput::Tas* tas_input; -}; - -TasButtonFactory::TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_) - : tas_input(std::move(tas_input_)) {} - -std::unique_ptr<Input::ButtonDevice> TasButtonFactory::Create(const Common::ParamPackage& params) { - const auto button_id = params.Get("button", 0); - const auto pad = params.Get("pad", 0); - - return std::make_unique<TasButton>(button_id, pad, tas_input.get()); -} - -class TasAnalog final : public Input::AnalogDevice { -public: - explicit TasAnalog(u32 pad_, u32 axis_x_, u32 axis_y_, const TasInput::Tas* tas_input_) - : pad(pad_), axis_x(axis_x_), axis_y(axis_y_), tas_input(tas_input_) {} - - float GetAxis(u32 axis) const { - std::lock_guard lock{mutex}; - return tas_input->GetTasState(pad).axis.at(axis); - } - - std::pair<float, float> GetAnalog(u32 analog_axis_x, u32 analog_axis_y) const { - float x = GetAxis(analog_axis_x); - float y = GetAxis(analog_axis_y); - - // Make sure the coordinates are in the unit circle, - // otherwise normalize it. - float r = x * x + y * y; - if (r > 1.0f) { - r = std::sqrt(r); - x /= r; - y /= r; - } - - return {x, y}; - } - - std::tuple<float, float> GetStatus() const override { - return GetAnalog(axis_x, axis_y); - } - - Input::AnalogProperties GetAnalogProperties() const override { - return {0.0f, 1.0f, 0.5f}; - } - -private: - const u32 pad; - const u32 axis_x; - const u32 axis_y; - const TasInput::Tas* tas_input; - mutable std::mutex mutex; -}; - -/// An analog device factory that creates analog devices from GC Adapter -TasAnalogFactory::TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_) - : tas_input(std::move(tas_input_)) {} - -/** - * Creates analog device from joystick axes - * @param params contains parameters for creating the device: - * - "port": the nth gcpad on the adapter - * - "axis_x": the index of the axis to be bind as x-axis - * - "axis_y": the index of the axis to be bind as y-axis - */ -std::unique_ptr<Input::AnalogDevice> TasAnalogFactory::Create(const Common::ParamPackage& params) { - const auto pad = static_cast<u32>(params.Get("pad", 0)); - const auto axis_x = static_cast<u32>(params.Get("axis_x", 0)); - const auto axis_y = static_cast<u32>(params.Get("axis_y", 1)); - - return std::make_unique<TasAnalog>(pad, axis_x, axis_y, tas_input.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/tas/tas_poller.h b/src/input_common/tas/tas_poller.h deleted file mode 100644 index 09e426cef..000000000 --- a/src/input_common/tas/tas_poller.h +++ /dev/null @@ -1,43 +0,0 @@ -// Copyright 2021 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "core/frontend/input.h" -#include "input_common/tas/tas_input.h" - -namespace InputCommon { - -/** - * A button device factory representing a tas bot. It receives tas events and forward them - * to all button devices it created. - */ -class TasButtonFactory final : public Input::Factory<Input::ButtonDevice> { -public: - explicit TasButtonFactory(std::shared_ptr<TasInput::Tas> tas_input_); - - /** - * Creates a button device from a button press - * @param params contains parameters for creating the device: - * - "code": the code of the key to bind with the button - */ - std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; - -private: - std::shared_ptr<TasInput::Tas> tas_input; -}; - -/// An analog device factory that creates analog devices from tas -class TasAnalogFactory final : public Input::Factory<Input::AnalogDevice> { -public: - explicit TasAnalogFactory(std::shared_ptr<TasInput::Tas> tas_input_); - - std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; - -private: - std::shared_ptr<TasInput::Tas> tas_input; -}; - -} // namespace InputCommon diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp deleted file mode 100644 index 7878a56d7..000000000 --- a/src/input_common/touch_from_button.cpp +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright 2020 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <algorithm> -#include "common/settings.h" -#include "core/frontend/framebuffer_layout.h" -#include "input_common/touch_from_button.h" - -namespace InputCommon { - -class TouchFromButtonDevice final : public Input::TouchDevice { -public: - TouchFromButtonDevice() { - const auto button_index = - static_cast<u64>(Settings::values.touch_from_button_map_index.GetValue()); - const auto& buttons = Settings::values.touch_from_button_maps[button_index].buttons; - - for (const auto& config_entry : buttons) { - const Common::ParamPackage package{config_entry}; - map.emplace_back( - Input::CreateDevice<Input::ButtonDevice>(config_entry), - std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)), - std::clamp(package.Get("y", 0), 0, - static_cast<int>(Layout::ScreenUndocked::Height))); - } - } - - Input::TouchStatus GetStatus() const override { - Input::TouchStatus touch_status{}; - for (std::size_t id = 0; id < map.size() && id < touch_status.size(); ++id) { - const bool state = std::get<0>(map[id])->GetStatus(); - if (state) { - const float x = static_cast<float>(std::get<1>(map[id])) / - static_cast<int>(Layout::ScreenUndocked::Width); - const float y = static_cast<float>(std::get<2>(map[id])) / - static_cast<int>(Layout::ScreenUndocked::Height); - touch_status[id] = {x, y, true}; - } - } - return touch_status; - } - -private: - // A vector of the mapped button, its x and its y-coordinate - std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map; -}; - -std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create(const Common::ParamPackage&) { - return std::make_unique<TouchFromButtonDevice>(); -} - -} // namespace InputCommon diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp deleted file mode 100644 index b9512aa2e..000000000 --- a/src/input_common/udp/client.cpp +++ /dev/null @@ -1,526 +0,0 @@ -// Copyright 2018 Citra Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <chrono> -#include <cstring> -#include <functional> -#include <random> -#include <thread> -#include <boost/asio.hpp> -#include "common/logging/log.h" -#include "common/settings.h" -#include "input_common/udp/client.h" -#include "input_common/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)); - SanitizeMotion(pad_data); - 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::PadData::Flags::AllPorts, - 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()); - } - - void SanitizeMotion(Response::PadData& data) { - // Zero out any non number value - if (!std::isnormal(data.gyro.pitch)) { - data.gyro.pitch = 0; - } - if (!std::isnormal(data.gyro.roll)) { - data.gyro.roll = 0; - } - if (!std::isnormal(data.gyro.yaw)) { - data.gyro.yaw = 0; - } - if (!std::isnormal(data.accel.x)) { - data.accel.x = 0; - } - if (!std::isnormal(data.accel.y)) { - data.accel.y = 0; - } - if (!std::isnormal(data.accel.z)) { - data.accel.z = 0; - } - } - - 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(); -} - -Client::Client() { - LOG_INFO(Input, "Udp Initialization started"); - finger_id.fill(MAX_TOUCH_FINGERS); - ReloadSockets(); -} - -Client::~Client() { - Reset(); -} - -Client::ClientConnection::ClientConnection() = default; - -Client::ClientConnection::~ClientConnection() = default; - -std::vector<Common::ParamPackage> Client::GetInputDevices() const { - std::vector<Common::ParamPackage> devices; - for (std::size_t pad = 0; pad < pads.size(); pad++) { - if (!DeviceConnected(pad)) { - continue; - } - std::string name = fmt::format("UDP Controller {}", pad); - devices.emplace_back(Common::ParamPackage{ - {"class", "cemuhookudp"}, - {"display", std::move(name)}, - {"port", std::to_string(pad)}, - }); - } - return devices; -} - -bool Client::DeviceConnected(std::size_t pad) const { - // Use last timestamp to detect if the socket has stopped sending data - const auto now = std::chrono::steady_clock::now(); - const auto time_difference = static_cast<u64>( - std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count()); - return time_difference < 1000 && pads[pad].connected; -} - -void Client::ReloadSockets() { - Reset(); - - std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers)); - 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 Client::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; -} - -void Client::OnVersion([[maybe_unused]] Response::Version data) { - LOG_TRACE(Input, "Version packet received: {}", data.version); -} - -void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) { - LOG_TRACE(Input, "PortInfo packet received: {}", data.model); -} - -void Client::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; - - const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; - pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); - // 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 - pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f); - pads[pad_index].motion.UpdateRotation(time_difference); - pads[pad_index].motion.UpdateOrientation(time_difference); - - { - std::lock_guard guard(pads[pad_index].status.update_mutex); - pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion(); - - for (std::size_t id = 0; id < data.touch.size(); ++id) { - UpdateTouchInput(data.touch[id], client, id); - } - - if (configuring) { - const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope(); - const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration(); - UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope); - } - } -} - -void Client::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].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()}; - - // Set motion parameters - // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode - // Real HW values are unknown, 0.0001 is an approximate to Standard - for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) { - pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f); - } -} - -void Client::Reset() { - for (auto& client : clients) { - if (client.thread.joinable()) { - client.active = -1; - client.socket->Stop(); - client.thread.join(); - } - } -} - -void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index, - const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro) { - if (gyro.Length() > 0.2f) { - LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client, - gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]); - } - UDPPadStatus pad{ - .host = clients[client].host, - .port = clients[client].port, - .pad_index = pad_index, - }; - for (std::size_t i = 0; i < 3; ++i) { - if (gyro[i] > 5.0f || gyro[i] < -5.0f) { - pad.motion = static_cast<PadMotion>(i); - pad.motion_value = gyro[i]; - pad_queue.Push(pad); - } - if (acc[i] > 1.75f || acc[i] < -1.75f) { - pad.motion = static_cast<PadMotion>(i + 3); - pad.motion_value = acc[i]; - pad_queue.Push(pad); - } - } -} - -std::optional<std::size_t> Client::GetUnusedFingerID() const { - std::size_t first_free_id = 0; - while (first_free_id < MAX_TOUCH_FINGERS) { - if (!std::get<2>(touch_status[first_free_id])) { - return first_free_id; - } else { - first_free_id++; - } - } - return std::nullopt; -} - -void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) { - // 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 std::size_t touch_id = client * 2 + id; - if (touch_pad.is_active) { - if (finger_id[touch_id] == MAX_TOUCH_FINGERS) { - const auto first_free_id = GetUnusedFingerID(); - if (!first_free_id) { - // Invalid finger id skip to next input - return; - } - finger_id[touch_id] = *first_free_id; - } - auto& [x, y, pressed] = touch_status[finger_id[touch_id]]; - x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) / - static_cast<float>(max_x - min_x); - y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) / - static_cast<float>(max_y - min_y); - pressed = true; - return; - } - - if (finger_id[touch_id] != MAX_TOUCH_FINGERS) { - touch_status[finger_id[touch_id]] = {}; - finger_id[touch_id] = MAX_TOUCH_FINGERS; - } -} - -void Client::BeginConfiguration() { - pad_queue.Clear(); - configuring = true; -} - -void Client::EndConfiguration() { - pad_queue.Clear(); - configuring = false; -} - -DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const { - const std::size_t client_number = GetClientNumber(host, port); - if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) { - return pads[0].status; - } - return pads[(client_number * PADS_PER_CLIENT) + pad].status; -} - -Input::TouchStatus& Client::GetTouchState() { - return touch_status; -} - -const Input::TouchStatus& Client::GetTouchState() const { - return touch_status; -} - -Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() { - return pad_queue; -} - -const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const { - return pad_queue; -} - -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] { - Status current_status{Status::Initialized}; - SocketCallback callback{ - [](Response::Version) {}, [](Response::PortInfo) {}, - [&](Response::PadData data) { - static constexpr u16 CALIBRATION_THRESHOLD = 100; - static constexpr u16 MAX_VALUE = UINT16_MAX; - - if (current_status == Status::Initialized) { - // Receiving data means the communication is ready now - current_status = Status::Ready; - status_callback(current_status); - } - const auto& touchpad_0 = data.touch[0]; - if (touchpad_0.is_active == 0) { - return; - } - LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y); - const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x)); - const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y)); - if (current_status == Status::Ready) { - // First touch - min data (min_x/min_y) - current_status = Status::Stage1Completed; - status_callback(current_status); - } - if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD && - touchpad_0.y - min_y > CALIBRATION_THRESHOLD) { - // Set the current position as max value and finishes configuration - const u16 max_x = touchpad_0.x; - const u16 max_y = touchpad_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 diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp deleted file mode 100644 index 9829da6f0..000000000 --- a/src/input_common/udp/udp.cpp +++ /dev/null @@ -1,110 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#include <mutex> -#include <utility> -#include "common/assert.h" -#include "common/threadsafe_queue.h" -#include "input_common/udp/client.h" -#include "input_common/udp/udp.h" - -namespace InputCommon { - -class UDPMotion final : public Input::MotionDevice { -public: - explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::MotionStatus GetStatus() const override { - return client->GetPadState(ip, port, pad).motion_status; - } - -private: - const std::string ip; - const u16 port; - const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast<u16>(params.Get("port", 26760)); - const auto pad = static_cast<u16>(params.Get("pad_index", 0)); - - return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get()); -} - -void UDPMotionFactory::BeginConfiguration() { - polling = true; - client->BeginConfiguration(); -} - -void UDPMotionFactory::EndConfiguration() { - polling = false; - client->EndConfiguration(); -} - -Common::ParamPackage UDPMotionFactory::GetNextInput() { - Common::ParamPackage params; - CemuhookUDP::UDPPadStatus pad; - auto& queue = client->GetPadQueue(); - while (queue.Pop(pad)) { - if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { - continue; - } - params.Set("engine", "cemuhookudp"); - params.Set("ip", pad.host); - params.Set("port", static_cast<u16>(pad.port)); - params.Set("pad_index", static_cast<u16>(pad.pad_index)); - params.Set("motion", static_cast<u16>(pad.motion)); - return params; - } - return params; -} - -class UDPTouch final : public Input::TouchDevice { -public: - explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_) - : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - - Input::TouchStatus GetStatus() const override { - return client->GetTouchState(); - } - -private: - const std::string ip; - [[maybe_unused]] const u16 port; - [[maybe_unused]] const u16 pad; - CemuhookUDP::Client* client; - mutable std::mutex mutex; -}; - -/// A motion device factory that creates motion devices from a UDP client -UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_) - : client(std::move(client_)) {} - -/** - * Creates motion device - * @param params contains parameters for creating the device: - * - "port": the UDP port number - */ -std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) { - auto ip = params.Get("ip", "127.0.0.1"); - const auto port = static_cast<u16>(params.Get("port", 26760)); - const auto pad = static_cast<u16>(params.Get("pad_index", 0)); - - return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get()); -} - -} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h deleted file mode 100644 index ea3fd4175..000000000 --- a/src/input_common/udp/udp.h +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2020 yuzu Emulator Project -// Licensed under GPLv2 or any later version -// Refer to the license.txt file included. - -#pragma once - -#include <memory> -#include "core/frontend/input.h" -#include "input_common/udp/client.h" - -namespace InputCommon { - -/// A motion device factory that creates motion devices from udp clients -class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { -public: - explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_); - - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<CemuhookUDP::Client> client; - bool polling = false; -}; - -/// A touch device factory that creates touch devices from udp clients -class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { -public: - explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_); - - std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; - - Common::ParamPackage GetNextInput(); - - /// For device input configuration/polling - void BeginConfiguration(); - void EndConfiguration(); - - bool IsPolling() const { - return polling; - } - -private: - std::shared_ptr<CemuhookUDP::Client> client; - bool polling = false; -}; - -} // namespace InputCommon |