From 5a785ed794fff8c944283271bf25cb835c11700a Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:18:40 -0500 Subject: input_common: Rewrite keyboard --- src/input_common/drivers/keyboard.cpp | 35 ++++++++++++++++++++++++++++ src/input_common/drivers/keyboard.h | 44 +++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 src/input_common/drivers/keyboard.cpp create mode 100644 src/input_common/drivers/keyboard.h (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp new file mode 100644 index 000000000..b00a4b8d9 --- /dev/null +++ b/src/input_common/drivers/keyboard.cpp @@ -0,0 +1,35 @@ +// 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/keyboard.h" + +namespace InputCommon { + +Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) { + PreSetController(identifier); +} + +void Keyboard::PressKey(int key_code) { + SetButton(identifier, key_code, true); +} + +void Keyboard::ReleaseKey(int key_code) { + SetButton(identifier, key_code, false); +} + +void Keyboard::ReleaseAllKeys() { + ResetButtonState(); +} + +std::vector Keyboard::GetInputDevices() const { + std::vector devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", "keyboard"}, + {"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..a3e0d8a61 --- /dev/null +++ b/src/input_common/drivers/keyboard.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 Keyboard final : public InputCommon::InputEngine { +public: + explicit Keyboard(const 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); + + void ReleaseAllKeys(); + + /// Used for automapping features + std::vector GetInputDevices() const override; + +private: + const PadIdentifier identifier = { + .guid = Common::UUID{""}, + .port = 0, + .pad = 0, + }; +}; + +} // namespace InputCommon -- cgit v1.2.3 From 00834b84dd954f6aea2354e07de2054a99f53fa4 Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:19:55 -0500 Subject: input_common: Rewrite mouse --- src/input_common/drivers/mouse.cpp | 138 +++++++++++++++++++++++++++++++++++++ src/input_common/drivers/mouse.h | 77 +++++++++++++++++++++ 2 files changed, 215 insertions(+) create mode 100644 src/input_common/drivers/mouse.cpp create mode 100644 src/input_common/drivers/mouse.h (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp new file mode 100644 index 000000000..2c2432fb7 --- /dev/null +++ b/src/input_common/drivers/mouse.cpp @@ -0,0 +1,138 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include +#include +#include + +#include "common/param_package.h" +#include "common/settings.h" +#include "common/thread.h" +#include "input_common/drivers/mouse.h" + +namespace InputCommon { +constexpr int touch_axis_x = 10; +constexpr int touch_axis_y = 11; + +Mouse::Mouse(const std::string input_engine_) : InputEngine(input_engine_) { + PreSetController(identifier); + 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) { + // Slow movement by 4% + last_mouse_change *= 0.96f; + const float sensitivity = + Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f; + SetAxis(identifier, 0, last_mouse_change.x * sensitivity); + SetAxis(identifier, 1, -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) { + 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(); + 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(x, y) - mouse_origin; + const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; + SetAxis(identifier, 0, static_cast(mouse_move.x) * sensitivity); + SetAxis(identifier, 1, static_cast(-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(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(button), false); + + if (!Settings::values.mouse_panning) { + SetAxis(identifier, 0, 0); + SetAxis(identifier, 1, 0); + } + button_pressed = false; +} + +void Mouse::ReleaseAllButtons() { + ResetButtonState(); + button_pressed = false; +} + +void Mouse::StopPanning() { + last_mouse_change = {}; +} + +std::vector Mouse::GetInputDevices() const { + std::vector devices; + devices.emplace_back(Common::ParamPackage{ + {"engine", "keyboard"}, + {"display", "Keyboard/Mouse"}, + }); + return devices; +} + +std::string Mouse::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return fmt::format("Mouse {}", params.Get("button", 0)); + } + + return "Bad Mouse"; +} + +} // namespace InputCommon diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h new file mode 100644 index 000000000..e8355751a --- /dev/null +++ b/src/input_common/drivers/mouse.h @@ -0,0 +1,77 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include +#include + +#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 InputCommon::InputEngine { +public: + explicit Mouse(const 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); + + void ReleaseAllButtons(); + + std::vector GetInputDevices() const override; + std::string GetUIName(const Common::ParamPackage& params) const override; + +private: + void UpdateThread(std::stop_token stop_token); + void StopPanning(); + + const PadIdentifier identifier = { + .guid = Common::UUID{""}, + .port = 0, + .pad = 0, + }; + Common::Vec2 mouse_origin; + Common::Vec2 last_mouse_position; + Common::Vec2 last_mouse_change; + bool button_pressed; + int mouse_panning_timout{}; + std::jthread update_thread; +}; + +} // namespace InputCommon -- cgit v1.2.3 From fa8e23b84281022bf6b4e8916231a17e9997e5aa Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:20:56 -0500 Subject: input_common: Rewrite touch --- src/input_common/drivers/touch_screen.cpp | 47 +++++++++++++++++++++++++++++ src/input_common/drivers/touch_screen.h | 50 +++++++++++++++++++++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 src/input_common/drivers/touch_screen.cpp create mode 100644 src/input_common/drivers/touch_screen.h (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp new file mode 100644 index 000000000..e13835e9f --- /dev/null +++ b/src/input_common/drivers/touch_screen.cpp @@ -0,0 +1,47 @@ +// 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 { + +TouchScreen::TouchScreen(const std::string input_engine_) : InputEngine(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(finger), true); + SetAxis(identifier, static_cast(finger * 2), x); + SetAxis(identifier, static_cast(finger * 2 + 1), y); +} + +void TouchScreen::TouchReleased(std::size_t finger) { + if (finger >= 16) { + return; + } + SetButton(identifier, static_cast(finger), false); + SetAxis(identifier, static_cast(finger * 2), 0.0f); + SetAxis(identifier, static_cast(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..5fbb2f47f --- /dev/null +++ b/src/input_common/drivers/touch_screen.h @@ -0,0 +1,50 @@ +// 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 InputCommon::InputEngine { +public: + explicit TouchScreen(const 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); + + void ReleaseAllTouch(); + +private: + const PadIdentifier identifier = { + .guid = Common::UUID{""}, + .port = 0, + .pad = 0, + }; +}; + +} // namespace InputCommon -- cgit v1.2.3 From 395e9a449d338e56ade9ea88ffeed297a7d86b10 Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:22:07 -0500 Subject: input_common: Rewrite gc_adapter --- src/input_common/drivers/gc_adapter.cpp | 483 ++++++++++++++++++++++++++++++++ src/input_common/drivers/gc_adapter.h | 128 +++++++++ src/input_common/drivers/tas_input.cpp | 320 +++++++++++++++++++++ src/input_common/drivers/tas_input.h | 200 +++++++++++++ 4 files changed, 1131 insertions(+) create mode 100644 src/input_common/drivers/gc_adapter.cpp create mode 100644 src/input_common/drivers/gc_adapter.h create mode 100644 src/input_common/drivers/tas_input.cpp create mode 100644 src/input_common/drivers/tas_input.h (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp new file mode 100644 index 000000000..6721ba4f7 --- /dev/null +++ b/src/input_common/drivers/gc_adapter.cpp @@ -0,0 +1,483 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include +#include + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "common/settings_input.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); + } + } + + LibUSBDeviceHandle& operator=(const LibUSBDeviceHandle&) = delete; + LibUSBDeviceHandle(const LibUSBDeviceHandle&) = delete; + + LibUSBDeviceHandle& operator=(LibUSBDeviceHandle&&) noexcept = delete; + LibUSBDeviceHandle(LibUSBDeviceHandle&&) noexcept = delete; + + [[nodiscard]] libusb_device_handle* get() noexcept { + return handle; + } + +private: + libusb_device_handle* handle{}; +}; + +GCAdapter::GCAdapter(const std::string input_engine_) : InputEngine(input_engine_) { + if (usb_adapter_handle) { + return; + } + LOG_INFO(Input, "GC Adapter Initialization started"); + + libusb_ctx = std::make_unique(); + const int init_res = libusb_ctx->InitResult(); + if (init_res == LIBUSB_SUCCESS) { + 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); + } +} + +GCAdapter::~GCAdapter() { + Reset(); +} + +void GCAdapter::AdapterInputThread(std::stop_token stop_token) { + LOG_DEBUG(Input, "GC Adapter input thread started"); + Common::SetCurrentThreadName("yuzu:input:GCAdapter"); + s32 payload_size{}; + AdapterPayload adapter_payload{}; + + adapter_scan_thread = {}; + + while (!stop_token.stop_requested()) { + libusb_interrupt_transfer(usb_adapter_handle->get(), input_endpoint, adapter_payload.data(), + static_cast(adapter_payload.size()), &payload_size, 16); + if (IsPayloadCorrect(adapter_payload, payload_size)) { + UpdateControllers(adapter_payload); + UpdateVibrations(); + } + std::this_thread::yield(); + } + + if (restart_scan_thread) { + adapter_scan_thread = + std::jthread([this](std::stop_token token) { AdapterScanThread(token); }); + restart_scan_thread = false; + } +} + +bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payload_size) { + if (payload_size != static_cast(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.request_stop(); + restart_scan_thread = true; + } + return false; + } + + input_error_counter = 0; + return true; +} + +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(adapter_payload[offset] >> 4); + UpdatePadType(port, type); + if (DeviceConnected(port)) { + const u8 b1 = adapter_payload[offset + 1]; + const u8 b2 = adapter_payload[offset + 2]; + UpdateStateButtons(port, b1, b2); + UpdateStateAxes(port, adapter_payload); + } + } +} + +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 + pads[port] = {}; + pads[port].type = pad_type; +} + +void GCAdapter::UpdateStateButtons(std::size_t port, [[maybe_unused]] u8 b1, + [[maybe_unused]] u8 b2) { + if (port >= pads.size()) { + return; + } + + static constexpr std::array b1_buttons{ + PadButton::ButtonA, PadButton::ButtonB, PadButton::ButtonX, PadButton::ButtonY, + PadButton::ButtonLeft, PadButton::ButtonRight, PadButton::ButtonDown, PadButton::ButtonUp, + }; + + static constexpr std::array b2_buttons{ + PadButton::ButtonStart, + PadButton::TriggerZ, + PadButton::TriggerR, + PadButton::TriggerL, + }; + + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { + const bool button_status = (b1 & (1U << i)) != 0; + const int button = static_cast(b1_buttons[i]); + SetButton(pads[port].identifier, button, button_status); + } + + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + const bool button_status = (b2 & (1U << j)) != 0; + const int button = static_cast(b2_buttons[j]); + SetButton(pads[port].identifier, button, button_status); + } +} + +void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_payload) { + if (port >= pads.size()) { + return; + } + + const std::size_t offset = 1 + (9 * port); + static constexpr std::array axes{ + PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, + PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, + }; + + for (const PadAxes axis : axes) { + const auto index = static_cast(axis); + const u8 axis_value = adapter_payload[offset + 3 + index]; + if (pads[port].reset_origin_counter <= 18) { + if (pads[port].axis_origin[index] != axis_value) { + pads[port].reset_origin_counter = 0; + } + pads[port].axis_origin[index] = axis_value; + pads[port].reset_origin_counter++; + } + const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 110.0f; + SetAxis(pads[port].identifier, static_cast(index), axis_status); + } +} + +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)); + } +} + +bool GCAdapter::Setup() { + constexpr u16 nintendo_vid = 0x057e; + constexpr u16 gc_adapter_pid = 0x0337; + usb_adapter_handle = + std::make_unique(libusb_ctx->get(), nintendo_vid, gc_adapter_pid); + if (!usb_adapter_handle->get()) { + return false; + } + if (!CheckDeviceAccess()) { + usb_adapter_handle = nullptr; + return false; + } + + 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)) { + rumble_enabled = true; + input_error_counter = 0; + output_error_counter = 0; + + std::size_t port = 0; + for (GCController& pad : pads) { + pad.identifier = { + .guid = Common::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; +} + +bool GCAdapter::CheckDeviceAccess() { + // 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); + } + + 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->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); + } + } + + if (kernel_driver_error && kernel_driver_error != LIBUSB_ERROR_NOT_SUPPORTED) { + usb_adapter_handle = nullptr; + return false; + } + + 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); + usb_adapter_handle = nullptr; + return false; + } + + return true; +} + +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) { + LOG_ERROR(Input, "libusb_get_config_descriptor failed with error = {}", + config_descriptor_return); + return false; + } + + for (u8 ic = 0; ic < config->bNumInterfaces; ic++) { + const libusb_interface* interfaceContainer = &config->interface[ic]; + for (int i = 0; i < interfaceContainer->num_altsetting; i++) { + const libusb_interface_descriptor* interface = &interfaceContainer->altsetting[i]; + for (u8 e = 0; e < interface->bNumEndpoints; e++) { + const libusb_endpoint_descriptor* endpoint = &interface->endpoint[e]; + if ((endpoint->bEndpointAddress & LIBUSB_ENDPOINT_IN) != 0) { + input_endpoint = endpoint->bEndpointAddress; + } else { + output_endpoint = endpoint->bEndpointAddress; + } + } + } + } + // 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->get(), output_endpoint, &clear_payload, + sizeof(clear_payload), nullptr, 16); + return true; +} + +bool GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { + const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; + const auto processed_amplitude = + static_cast((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); + + pads[identifier.port].rumble_amplitude = processed_amplitude; + return rumble_enabled; +} + +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 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 payload = {rumble_command, p1, p2, p3, p4}; + const int err = + libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(), + static_cast(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; + } + output_error_counter = 0; + vibration_changed = false; +} + +bool GCAdapter::DeviceConnected(std::size_t port) const { + return pads[port].type != ControllerTypes::None; +} + +void GCAdapter::Reset() { + adapter_scan_thread = {}; + adapter_input_thread = {}; + usb_adapter_handle = nullptr; + pads = {}; + libusb_ctx = nullptr; +} + +std::vector GCAdapter::GetInputDevices() const { + std::vector devices; + for (std::size_t port = 0; port < pads.size(); ++port) { + if (!DeviceConnected(port)) { + continue; + } + const std::string name = fmt::format("Gamecube Controller {}", port + 1); + devices.emplace_back(Common::ParamPackage{ + {"engine", "gcpad"}, + {"display", std::move(name)}, + {"port", std::to_string(port)}, + }); + } + return devices; +} + +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 + static constexpr std::array, 12> + switch_to_gcadapter_button = { + std::pair{Settings::NativeButton::A, PadButton::ButtonA}, + {Settings::NativeButton::B, PadButton::ButtonB}, + {Settings::NativeButton::X, PadButton::ButtonX}, + {Settings::NativeButton::Y, PadButton::ButtonY}, + {Settings::NativeButton::Plus, PadButton::ButtonStart}, + {Settings::NativeButton::DLeft, PadButton::ButtonLeft}, + {Settings::NativeButton::DUp, PadButton::ButtonUp}, + {Settings::NativeButton::DRight, PadButton::ButtonRight}, + {Settings::NativeButton::DDown, PadButton::ButtonDown}, + {Settings::NativeButton::SL, PadButton::TriggerL}, + {Settings::NativeButton::SR, PadButton::TriggerR}, + {Settings::NativeButton::R, PadButton::TriggerZ}, + }; + if (!params.Has("port")) { + return {}; + } + + ButtonMapping mapping{}; + for (const auto& [switch_button, gcadapter_button] : switch_to_gcadapter_button) { + Common::ParamPackage button_params({{"engine", "gcpad"}}); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("button", static_cast(gcadapter_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + // Add the missing bindings for ZL/ZR + static constexpr std::array, 2> + switch_to_gcadapter_axis = { + std::tuple{Settings::NativeButton::ZL, PadButton::TriggerL, PadAxes::TriggerLeft}, + {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight}, + }; + for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) { + Common::ParamPackage button_params({{"engine", "gcpad"}}); + button_params.Set("port", params.Get("port", 0)); + button_params.Set("button", static_cast(gcadapter_buton)); + button_params.Set("axis", static_cast(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; +} + +AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("port")) { + return {}; + } + + AnalogMapping mapping = {}; + Common::ParamPackage left_analog_params; + left_analog_params.Set("engine", "gcpad"); + left_analog_params.Set("port", params.Get("port", 0)); + left_analog_params.Set("axis_x", static_cast(PadAxes::StickX)); + left_analog_params.Set("axis_y", static_cast(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("port", params.Get("port", 0)); + right_analog_params.Set("axis_x", static_cast(PadAxes::SubstickX)); + right_analog_params.Set("axis_y", static_cast(PadAxes::SubstickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +std::string GCAdapter::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + return fmt::format("Button {}", params.Get("button", 0)); + } + + return "Bad GC Adapter"; +} + +} // 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..c0bf1ed7a --- /dev/null +++ b/src/input_common/drivers/gc_adapter.h @@ -0,0 +1,128 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include + +#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 InputCommon::InputEngine { +public: + explicit GCAdapter(const std::string input_engine_); + ~GCAdapter(); + + bool SetRumble(const PadIdentifier& identifier, + const Input::VibrationStatus vibration) override; + + /// Used for automapping features + std::vector GetInputDevices() const override; + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + std::string 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 axis_origin{}; + u8 reset_origin_counter{}; + }; + + using AdapterPayload = std::array; + + 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(); + std::unique_ptr usb_adapter_handle; + std::array pads; + + std::jthread adapter_input_thread; + std::jthread adapter_scan_thread; + bool restart_scan_thread{}; + + std::unique_ptr 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/tas_input.cpp b/src/input_common/drivers/tas_input.cpp new file mode 100644 index 000000000..5e2101b27 --- /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 +#include +#include + +#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 TasAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + Undefined, +}; + +// Supported keywords and buttons from a TAS file +constexpr std::array, 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(const std::string input_engine_) : InputCommon::InputEngine(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); + 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; + } + std::smatch m; + + std::stringstream linestream(line); + std::string segment; + std::vector 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++) { + 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 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()); + } +} + +void Tas::RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis) { + last_input = { + .buttons = buttons, + .l_axis = FlipAxisY(left_axis), + .r_axis = FlipAxisY(right_axis), + }; +} + +TasAnalog Tas::FlipAxisY(TasAnalog old) { + return { + .x = old.x, + .y = -old.y, + }; +} + +std::tuple 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); + 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); ++i) { + const bool button_status = (command.buttons & (1U << i)) != 0; + const int button = static_cast(i); + SetButton(identifier, button, button_status); + } + SetAxis(identifier, TasAxes::StickX, command.l_axis.x); + SetAxis(identifier, TasAxes::StickY, command.l_axis.y); + SetAxis(identifier, TasAxes::SubstickX, command.r_axis.x); + SetAxis(identifier, TasAxes::SubstickY, command.r_axis.y); + } + } else { + is_running = Settings::values.tas_loop.GetValue(); + current_command = 0; + ClearInput(); + } +} + +void Tas::ClearInput() { + ResetButtonState(); + ResetAnalogState(); +} + +TasAnalog Tas::ReadCommandAxis(const std::string& line) const { + std::stringstream linestream(line); + std::string segment; + std::vector 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(tas_button); + break; + } + } + } + return buttons; +} + +std::string Tas::WriteCommandButtons(u32 buttons) const { + std::string returns = ""; + for (auto [text_button, tas_button] : text_to_tas_button) { + if ((buttons & static_cast(tas_button)) != 0) + returns += fmt::format("{};", text_button.substr(4)); + } + return returns.empty() ? "NONE" : returns.substr(2); +} + +std::string Tas::WriteCommandAxis(TasAnalog analog) const { + return fmt::format("{};{}", analog.x * 32767, analog.y * 32767); +} + +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..9fadc118b --- /dev/null +++ b/src/input_common/drivers/tas_input.h @@ -0,0 +1,200 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/settings_input.h" +#include "input_common/input_engine.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 InputCommon::TasInput { + +constexpr size_t PLAYER_NUMBER = 10; + +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, +}; + +struct TasAnalog { + float x{}; + float y{}; +}; + +enum class TasState { + Running, + Recording, + Stopped, +}; + +class Tas final : public InputCommon::InputEngine { +public: + explicit Tas(const std::string input_engine_); + ~Tas(); + + /** + * 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(u32 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 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 + * @param overwrite_file: Indicates if player 1 should be overwritten + */ + 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 ; + * Current playback progress ; + * Total length of script file currently loaded or being recorded + */ + std::tuple GetStatus() 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 + * @param player_index: player number where data is going to be stored + */ + void LoadTasFile(size_t player_index); + + /** Writes a TAS file from the recorded commands + * @param file_name: name of the file to be written + */ + void WriteTasFile(std::u8string file_name); + + /** Inverts the Y axis polarity + * @param old: value of the axis + * @return new value of the axis + */ + TasAnalog FlipAxisY(TasAnalog old); + + /** + * 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" + * @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. 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..." + * @return Returns a u32 with each bit representing the status of a button + */ + u32 ReadCommandButtons(const std::string& line) const; + + /** + * Reset state of all players + */ + void ClearInput(); + + /** + * Converts an u32 containing the button status into the text equivalent + * @param buttons: bitfield with the status of the buttons + * @return Returns a string with the name of the buttons to be written to the file + */ + std::string WriteCommandButtons(u32 buttons) const; + + /** + * Converts an TAS analog object containing the axis status into the text equivalent + * @param data: value of the axis + * @return A string with the value of the axis to be written to the file + */ + std::string WriteCommandAxis(TasAnalog data) const; + + size_t script_length{0}; + bool is_old_input_saved{false}; + bool is_recording{false}; + bool is_running{false}; + bool needs_reset{false}; + std::array, PLAYER_NUMBER> commands{}; + std::vector record_commands{}; + size_t current_command{0}; + TASCommand last_input{}; // only used for recording +}; +} // namespace InputCommon::TasInput -- cgit v1.2.3 From 10241886ddbead1a24a5924debf83a4fad0ade0d Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:33:19 -0500 Subject: input_common: Rewrite udp client --- src/input_common/drivers/udp_client.cpp | 364 ++++++++++++++++++++++++++++++++ src/input_common/drivers/udp_client.h | 127 +++++++++++ 2 files changed, 491 insertions(+) create mode 100644 src/input_common/drivers/udp_client.cpp create mode 100644 src/input_common/drivers/udp_client.h (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp new file mode 100644 index 000000000..6fcc3a01b --- /dev/null +++ b/src/input_common/drivers/udp_client.cpp @@ -0,0 +1,364 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include +#include + +#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 version; + std::function port_info; + std::function 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::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()); + } + + SocketCallback callback; + boost::asio::io_service io_service; + boost::asio::basic_waitable_timer timer; + udp::socket socket; + + const u32 client_id; + + static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message); + static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message); + std::array send_buffer1; + std::array send_buffer2; + udp::endpoint send_endpoint; + + std::array receive_buffer; + udp::endpoint receive_endpoint; +}; + +static void SocketLoop(Socket* socket) { + socket->StartReceive(); + socket->StartSend(Socket::clock::now()); + socket->Loop(); +} + +UDPClient::UDPClient(const std::string& input_engine_) : InputEngine(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(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( + std::chrono::duration_cast(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); +} + +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].host = host; + clients[client].port = port; + clients[client].active = 0; + clients[client].socket = std::make_unique(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 = Common::UUID{clients[client].host}, + .port = static_cast(clients[client].port), + .pad = pad_index, + }; +} + +void UDPClient::Reset() { + for (auto& client : clients) { + if (client.thread.joinable()) { + client.active = -1; + client.socket->Stop(); + client.thread.join(); + } + } +} + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& 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 status_callback, + std::function 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(touchpad_0.x)); + const u16 min_y = std::min(MAX_VALUE, static_cast(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/drivers/udp_client.h b/src/input_common/drivers/udp_client.h new file mode 100644 index 000000000..58b2e921d --- /dev/null +++ b/src/input_common/drivers/udp_client.h @@ -0,0 +1,127 @@ +// Copyright 2021 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include + +#include "common/common_types.h" +#include "common/thread.h" +#include "input_common/input_engine.h" + +namespace InputCommon::CemuhookUDP { + +class Socket; + +namespace Response { +struct PadData; +struct PortInfo; +struct TouchPad; +struct Version; +} // namespace Response + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + std::string host{"127.0.0.1"}; + u16 port{26760}; + std::size_t pad_index{}; +}; + +struct DeviceStatus { + std::mutex update_mutex; + + // calibration data for scaling the device's touch area to 3ds + struct CalibrationData { + u16 min_x{}; + u16 min_y{}; + u16 max_x{}; + u16 max_y{}; + }; + std::optional touch_calibration; +}; + +/** + * A button device factory representing a keyboard. It receives keyboard events and forward them + * to all button devices it created. + */ +class UDPClient final : public InputCommon::InputEngine { +public: + explicit UDPClient(const std::string& input_engine_); + ~UDPClient(); + + void ReloadSockets(); + +private: + struct PadData { + std::size_t pad_index{}; + bool connected{}; + DeviceStatus status; + u64 packet_sequence{}; + + std::chrono::time_point last_update; + }; + + struct ClientConnection { + ClientConnection(); + ~ClientConnection(); + std::string host{"127.0.0.1"}; + u16 port{26760}; + s8 active{-1}; + std::unique_ptr socket; + std::thread thread; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + + // Translates configuration to client number + std::size_t GetClientNumber(std::string_view host, u16 port) const; + + void OnVersion(Response::Version); + void OnPortInfo(Response::PortInfo); + void OnPadData(Response::PadData, std::size_t client); + void StartCommunication(std::size_t client, const std::string& host, u16 port); + const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; + + // Allocate clients for 8 udp servers + static constexpr std::size_t MAX_UDP_CLIENTS = 8; + static constexpr std::size_t PADS_PER_CLIENT = 4; + std::array pads{}; + std::array clients{}; +}; + +/// An async job allowing configuration of the touchpad calibration. +class CalibrationConfigurationJob { +public: + enum class Status { + Initialized, + Ready, + Stage1Completed, + Completed, + }; + /** + * Constructs and starts the job with the specified parameter. + * + * @param status_callback Callback for job status updates + * @param data_callback Called when calibration data is ready + */ + explicit CalibrationConfigurationJob(const std::string& host, u16 port, + std::function status_callback, + std::function data_callback); + ~CalibrationConfigurationJob(); + void Stop(); + +private: + Common::Event complete_event; +}; + +void TestCommunication(const std::string& host, u16 port, + const std::function& success_callback, + const std::function& failure_callback); + +} // namespace InputCommon::CemuhookUDP -- cgit v1.2.3 From 59b995a9e53f09b9831b03608cfe5ce27c3e3485 Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 20 Sep 2021 17:36:23 -0500 Subject: input_common: Rewrite SDL --- src/input_common/drivers/sdl_driver.cpp | 915 ++++++++++++++++++++++++++++++++ src/input_common/drivers/sdl_driver.h | 120 +++++ 2 files changed, 1035 insertions(+) create mode 100644 src/input_common/drivers/sdl_driver.cpp create mode 100644 src/input_common/drivers/sdl_driver.h (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp new file mode 100644 index 000000000..efb4a2106 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.cpp @@ -0,0 +1,915 @@ +// 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(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; + } + + BasicMotion GetMotion() { + return motion; + } + + bool RumblePlay(const Input::VibrationStatus vibration) { + constexpr u32 rumble_max_duration_ms = 1000; + + if (sdl_controller) { + return SDL_GameControllerRumble( + sdl_controller.get(), static_cast(vibration.low_amplitude), + static_cast(vibration.high_amplitude), rumble_max_duration_ms) != -1; + } else if (sdl_joystick) { + return SDL_JoystickRumble(sdl_joystick.get(), static_cast(vibration.low_amplitude), + static_cast(vibration.high_amplitude), + rumble_max_duration_ms) != -1; + } + + return false; + } + /** + * The Pad identifier of the joystick + */ + const PadIdentifier GetPadIdentifier() const { + return { + .guid = Common::UUID{guid}, + .port = static_cast(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"; + } + + bool IsYAxis(u8 index) { + if (!sdl_controller) { + return false; + } + + const auto& binding_left_y = + SDL_GameControllerGetBindForAxis(sdl_controller.get(), SDL_CONTROLLER_AXIS_LEFTY); + const auto& binding_right_y = + SDL_GameControllerGetBindForAxis(sdl_controller.get(), SDL_CONTROLLER_AXIS_RIGHTY); + if (index == binding_left_y.value.axis) { + return true; + } + if (index == binding_right_y.value.axis) { + return true; + } + return false; + } + +private: + std::string guid; + int port; + std::unique_ptr sdl_joystick; + std::unique_ptr sdl_controller; + mutable std::mutex mutex; + + u64 last_motion_update{}; + bool has_gyro{false}; + bool has_accel{false}; + BasicMotion motion; +}; + +std::shared_ptr 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(port)) { + auto joystick = std::make_shared(guid, static_cast(it->second.size()), + nullptr, nullptr); + it->second.emplace_back(std::move(joystick)); + } + + return it->second[static_cast(port)]; + } + + auto joystick = std::make_shared(guid, 0, nullptr, nullptr); + + return joystick_map[guid].emplace_back(std::move(joystick)); +} + +std::shared_ptr 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(guid, 0, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + 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(joystick_guid_list.size()); + auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); + PreSetController(joystick->GetPadIdentifier()); + 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(); + // Vertical axis is inverted on nintendo compared to SDL + if (joystick->IsYAxis(event.jaxis.axis)) { + SetAxis(identifier, event.jaxis.axis, -event.jaxis.value / 32767.0f); + break; + } + 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(const std::string& input_engine_) : InputEngine(input_engine_) { + Common::SetCurrentThreadName("yuzu:input:SDL"); + + 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_JOYSTICK_HIDAPI_JOY_CONS", "1"); + + // Turn off Pro controller home led + SDL_SetHint("SDL_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 | 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] { + 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 SDLDriver::GetInputDevices() const { + std::vector devices; + std::unordered_map> 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", "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()); + + const std::string name = + fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"engine", "sdl"}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"guid2", joystick2->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } + } + } + return devices; +} +bool SDLDriver::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { + const auto joystick = + GetSDLJoystickByGUID(identifier.guid.Format(), static_cast(identifier.port)); + const auto process_amplitude = [](f32 amplitude) { + return (amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF; + }; + const Input::VibrationStatus new_vibration{ + .low_amplitude = process_amplitude(vibration.low_amplitude), + .low_frequency = vibration.low_frequency, + .high_amplitude = process_amplitude(vibration.high_amplitude), + .high_frequency = vibration.high_frequency, + }; + + return joystick->RumblePlay(new_vibration); +} +Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, + s32 axis, float value) const { + Common::ParamPackage params({{"engine", "sdl"}}); + 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({{"engine", "sdl"}}); + 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({{"engine", "sdl"}}); + + 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({{"engine", "sdl"}, {"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(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", "sdl"); + params.Set("port", static_cast(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& 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& 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& joystick, + const std::shared_ptr& 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; +} + +std::string SDLDriver::GetUIName(const Common::ParamPackage& params) const { + if (params.Has("button")) { + // TODO(German77): Find how to substitue the values for real button names + return fmt::format("Button {}", params.Get("button", 0)); + } + if (params.Has("hat")) { + return fmt::format("Hat {}", params.Get("direction", "")); + } + if (params.Has("axis")) { + return fmt::format("Axis {}", params.Get("axis", "")); + } + if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { + return fmt::format("Axis {},{},{}", params.Get("axis_x", ""), params.Get("axis_y", ""), + params.Get("axis_z", "")); + } + if (params.Has("motion")) { + return "SDL motion"; + } + + return "Bad SDL"; +} + +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/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h new file mode 100644 index 000000000..d8d350184 --- /dev/null +++ b/src/input_common/drivers/sdl_driver.h @@ -0,0 +1,120 @@ +// Copyright 2018 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include +#include +#include +#include + +#include + +#include "common/common_types.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; + +using ButtonBindings = + std::array, 17>; +using ZButtonBindings = + std::array, 2>; + +namespace InputCommon { + +class SDLJoystick; + +class SDLDriver : public InputCommon::InputEngine { +public: + /// Initializes and registers SDL device factories + SDLDriver(const std::string& input_engine_); + + /// Unregisters SDL device factories and shut them down. + ~SDLDriver() override; + + /// Handle SDL_Events for joysticks from SDL_PollEvent + void HandleGameControllerEvent(const SDL_Event& event); + + /// Get the nth joystick with the corresponding GUID + std::shared_ptr GetSDLJoystickBySDLID(SDL_JoystickID sdl_id); + + /** + * Check how many identical joysticks (by guid) were connected before the one with sdl_id and so + * tie it to a SDLJoystick with the same guid and that port + */ + std::shared_ptr GetSDLJoystickByGUID(const std::string& guid, int port); + + std::vector GetInputDevices() const override; + + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; + std::string GetUIName(const Common::ParamPackage& params) const override; + + std::string GetHatButtonName(u8 direction_value) const override; + u8 GetHatButtonId(const std::string direction_name) const override; + + bool SetRumble(const PadIdentifier& identifier, + const Input::VibrationStatus vibration) override; + +private: + void InitJoystick(int joystick_index); + void CloseJoystick(SDL_Joystick* sdl_joystick); + + /// 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; + + /// Returns the default button bindings list for nintendo controllers + ButtonBindings GetNintendoButtonBinding(const std::shared_ptr& joystick) const; + + /// Returns the button mappings from a single controller + ButtonMapping GetSingleControllerMapping(const std::shared_ptr& joystick, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const; + + /// Returns the button mappings from two different controllers + ButtonMapping GetDualControllerMapping(const std::shared_ptr& joystick, + const std::shared_ptr& joystick2, + const ButtonBindings& switch_to_sdl_button, + const ZButtonBindings& switch_to_sdl_axis) const; + + /// 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>> joystick_map; + std::mutex joystick_map_mutex; + + bool start_thread = false; + std::atomic initialized = false; + + std::thread poll_thread; +}; +} // namespace InputCommon -- cgit v1.2.3 From 06a5ef5874144a70e30e577a83ba68d1dad79e78 Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 11 Oct 2021 00:43:11 -0500 Subject: core/hid: Add output devices --- src/input_common/drivers/gc_adapter.cpp | 8 ++++++-- src/input_common/drivers/gc_adapter.h | 2 +- src/input_common/drivers/sdl_driver.cpp | 8 ++++++-- src/input_common/drivers/sdl_driver.h | 2 +- 4 files changed, 14 insertions(+), 6 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 6721ba4f7..2aa5a16a6 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -322,13 +322,17 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { return true; } -bool GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { +Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; const auto processed_amplitude = static_cast((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); pads[identifier.port].rumble_amplitude = processed_amplitude; - return rumble_enabled; + + if (!rumble_enabled) { + return Input::VibrationError::Disabled; + } + return Input::VibrationError::None; } void GCAdapter::UpdateVibrations() { diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index c0bf1ed7a..dd23dd9f3 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -24,7 +24,7 @@ public: explicit GCAdapter(const std::string input_engine_); ~GCAdapter(); - bool SetRumble(const PadIdentifier& identifier, + Input::VibrationError SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) override; /// Used for automapping features diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index efb4a2106..f7f03c5f2 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -506,7 +506,8 @@ std::vector SDLDriver::GetInputDevices() const { } return devices; } -bool SDLDriver::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { +Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, + const Input::VibrationStatus vibration) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.Format(), static_cast(identifier.port)); const auto process_amplitude = [](f32 amplitude) { @@ -519,7 +520,10 @@ bool SDLDriver::SetRumble(const PadIdentifier& identifier, const Input::Vibratio .high_frequency = vibration.high_frequency, }; - return joystick->RumblePlay(new_vibration); + if (!joystick->RumblePlay(new_vibration)) { + return Input::VibrationError::Unknown; + } + return Input::VibrationError::None; } Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, float value) const { diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index d8d350184..f66b33c77 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -58,7 +58,7 @@ public: std::string GetHatButtonName(u8 direction_value) const override; u8 GetHatButtonId(const std::string direction_name) const override; - bool SetRumble(const PadIdentifier& identifier, + Input::VibrationError SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) override; private: -- cgit v1.2.3 From e0da5c1bbcdf85676f968b63c8ae2587f0464193 Mon Sep 17 00:00:00 2001 From: german77 Date: Fri, 15 Oct 2021 19:07:47 -0500 Subject: kraken: Fix errors from rebase and format files --- src/input_common/drivers/gc_adapter.cpp | 3 ++- src/input_common/drivers/gc_adapter.h | 2 +- src/input_common/drivers/sdl_driver.h | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 2aa5a16a6..4a56abb99 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -322,7 +322,8 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { return true; } -Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { +Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, + const Input::VibrationStatus vibration) { const auto mean_amplitude = (vibration.low_amplitude + vibration.high_amplitude) * 0.5f; const auto processed_amplitude = static_cast((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index dd23dd9f3..dd0e4aa1d 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -25,7 +25,7 @@ public: ~GCAdapter(); Input::VibrationError SetRumble(const PadIdentifier& identifier, - const Input::VibrationStatus vibration) override; + const Input::VibrationStatus vibration) override; /// Used for automapping features std::vector GetInputDevices() const override; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index f66b33c77..1ff85f48d 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -59,7 +59,7 @@ public: u8 GetHatButtonId(const std::string direction_name) const override; Input::VibrationError SetRumble(const PadIdentifier& identifier, - const Input::VibrationStatus vibration) override; + const Input::VibrationStatus vibration) override; private: void InitJoystick(int joystick_index); -- cgit v1.2.3 From 601ac43495904f3f7666d79a800a8b4eda5a8461 Mon Sep 17 00:00:00 2001 From: german77 Date: Tue, 19 Oct 2021 00:12:24 -0500 Subject: core/hid: Only signal when needed --- src/input_common/drivers/gc_adapter.cpp | 75 +++++++++++++++++++++++++++------ src/input_common/drivers/gc_adapter.h | 6 ++- src/input_common/drivers/keyboard.cpp | 2 +- src/input_common/drivers/sdl_driver.cpp | 22 ++++++---- src/input_common/drivers/tas_input.cpp | 2 +- 5 files changed, 84 insertions(+), 23 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 4a56abb99..4fb6ab5af 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -150,7 +150,10 @@ void GCAdapter::UpdatePadType(std::size_t port, ControllerTypes pad_type) { return; } // Device changed reset device and set new type - pads[port] = {}; + pads[port].axis_origin = {}; + pads[port].reset_origin_counter = {}; + pads[port].enable_vibration = {}; + pads[port].rumble_amplitude = {}; pads[port].type = pad_type; } @@ -396,12 +399,11 @@ std::vector GCAdapter::GetInputDevices() const { if (!DeviceConnected(port)) { continue; } - const std::string name = fmt::format("Gamecube Controller {}", port + 1); - devices.emplace_back(Common::ParamPackage{ - {"engine", "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(port)); + devices.emplace_back(identifier); } return devices; } @@ -431,7 +433,8 @@ ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& p 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(gcadapter_button)); mapping.insert_or_assign(switch_button, std::move(button_params)); @@ -444,7 +447,8 @@ ButtonMapping GCAdapter::GetButtonMappingForDevice(const Common::ParamPackage& p {Settings::NativeButton::ZR, PadButton::TriggerR, PadAxes::TriggerRight}, }; for (const auto& [switch_button, gcadapter_buton, gcadapter_axis] : switch_to_gcadapter_axis) { - 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(gcadapter_buton)); button_params.Set("axis", static_cast(gcadapter_axis)); @@ -463,13 +467,13 @@ AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& p 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(PadAxes::StickX)); left_analog_params.Set("axis_y", static_cast(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(PadAxes::SubstickX)); right_analog_params.Set("axis_y", static_cast(PadAxes::SubstickY)); @@ -477,9 +481,56 @@ AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& p return mapping; } +std::string GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { + PadButton button = static_cast(params.Get("button", 0)); + switch (button) { + case PadButton::ButtonLeft: + return "left"; + break; + case PadButton::ButtonRight: + return "right"; + break; + case PadButton::ButtonDown: + return "down"; + break; + case PadButton::ButtonUp: + return "up"; + break; + case PadButton::TriggerZ: + return "Z"; + break; + case PadButton::TriggerR: + return "R"; + break; + case PadButton::TriggerL: + return "L"; + break; + case PadButton::ButtonA: + return "A"; + break; + case PadButton::ButtonB: + return "B"; + break; + case PadButton::ButtonX: + return "X"; + break; + case PadButton::ButtonY: + return "Y"; + break; + case PadButton::ButtonStart: + return "start"; + break; + default: + return "Unkown GC"; + } +} + std::string GCAdapter::GetUIName(const Common::ParamPackage& params) const { if (params.Has("button")) { - return fmt::format("Button {}", params.Get("button", 0)); + return fmt::format("Button {}", GetUIButtonName(params)); + } + if (params.Has("axis")) { + return fmt::format("Axis {}", params.Get("axis",0)); } return "Bad GC Adapter"; diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index dd0e4aa1d..b82e4803d 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -105,8 +105,12 @@ private: void Reset(); void UpdateVibrations(); - // Updates vibration state of all controllers + + /// Updates vibration state of all controllers void SendVibrations(); + + std::string GetUIButtonName(const Common::ParamPackage& params) const; + std::unique_ptr usb_adapter_handle; std::array pads; diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp index b00a4b8d9..85a781a30 100644 --- a/src/input_common/drivers/keyboard.cpp +++ b/src/input_common/drivers/keyboard.cpp @@ -26,7 +26,7 @@ void Keyboard::ReleaseAllKeys() { std::vector Keyboard::GetInputDevices() const { std::vector devices; devices.emplace_back(Common::ParamPackage{ - {"engine", "keyboard"}, + {"engine", GetEngineName()}, {"display", "Keyboard Only"}, }); return devices; diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index f7f03c5f2..cee2d965f 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -305,6 +305,7 @@ void SDLDriver::InitJoystick(int joystick_index) { if (joystick_map.find(guid) == joystick_map.end()) { auto joystick = std::make_shared(guid, 0, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); + SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -322,6 +323,7 @@ void SDLDriver::InitJoystick(int joystick_index) { const int port = static_cast(joystick_guid_list.size()); auto joystick = std::make_shared(guid, port, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); + SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); joystick_guid_list.emplace_back(std::move(joystick)); } @@ -472,7 +474,7 @@ std::vector SDLDriver::GetInputDevices() const { const std::string name = fmt::format("{} {}", joystick->GetControllerName(), joystick->GetPort()); devices.emplace_back(Common::ParamPackage{ - {"engine", "sdl"}, + {"engine", GetEngineName()}, {"display", std::move(name)}, {"guid", joystick->GetGUID()}, {"port", std::to_string(joystick->GetPort())}, @@ -495,7 +497,7 @@ std::vector SDLDriver::GetInputDevices() const { const std::string name = fmt::format("{} {}", "Nintendo Dual Joy-Con", joystick->GetPort()); devices.emplace_back(Common::ParamPackage{ - {"engine", "sdl"}, + {"engine", GetEngineName()}, {"display", std::move(name)}, {"guid", joystick->GetGUID()}, {"guid2", joystick2->GetGUID()}, @@ -527,7 +529,8 @@ Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, } Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, float value) const { - Common::ParamPackage params({{"engine", "sdl"}}); + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); params.Set("port", port); params.Set("guid", std::move(guid)); params.Set("axis", axis); @@ -538,7 +541,8 @@ Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std:: Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std::string guid, s32 button) const { - Common::ParamPackage params({{"engine", "sdl"}}); + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); params.Set("port", port); params.Set("guid", std::move(guid)); params.Set("button", button); @@ -547,8 +551,8 @@ Common::ParamPackage SDLDriver::BuildButtonParamPackageForButton(int port, std:: Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::string guid, s32 hat, u8 value) const { - Common::ParamPackage params({{"engine", "sdl"}}); - + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); params.Set("port", port); params.Set("guid", std::move(guid)); params.Set("hat", hat); @@ -557,7 +561,9 @@ Common::ParamPackage SDLDriver::BuildHatParamPackageForButton(int port, std::str } Common::ParamPackage SDLDriver::BuildMotionParam(int port, std::string guid) const { - Common::ParamPackage params({{"engine", "sdl"}, {"motion", "0"}}); + Common::ParamPackage params{}; + params.Set("engine", GetEngineName()); + params.Set("motion", 0); params.Set("port", port); params.Set("guid", std::move(guid)); return params; @@ -583,7 +589,7 @@ Common::ParamPackage SDLDriver::BuildParamPackageForAnalog(PadIdentifier identif int axis_y, float offset_x, float offset_y) const { Common::ParamPackage params; - params.Set("engine", "sdl"); + params.Set("engine", GetEngineName()); params.Set("port", static_cast(identifier.port)); params.Set("guid", identifier.guid.Format()); params.Set("axis_x", axis_x); diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index 5e2101b27..7e7a1d58f 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp @@ -127,7 +127,7 @@ void Tas::WriteTasFile(std::u8string 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), + output_text += fmt::format("{} {} {} {}\n", frame, WriteCommandButtons(line.buttons), WriteCommandAxis(line.l_axis), WriteCommandAxis(line.r_axis)); } const auto bytes_written = Common::FS::WriteStringToFile( -- cgit v1.2.3 From c3ff0a8ac0d1c3f9c0791b5263dae53c06ad6048 Mon Sep 17 00:00:00 2001 From: german77 Date: Wed, 20 Oct 2021 14:41:56 -0500 Subject: core/hid: Fix rumble too strong at 1% --- src/input_common/drivers/sdl_driver.cpp | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index cee2d965f..d56351815 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -107,6 +107,14 @@ public: 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 */ @@ -515,16 +523,26 @@ Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, const auto process_amplitude = [](f32 amplitude) { return (amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF; }; - const Input::VibrationStatus new_vibration{ + const Input::VibrationStatus exponential_vibration{ .low_amplitude = process_amplitude(vibration.low_amplitude), .low_frequency = vibration.low_frequency, .high_amplitude = process_amplitude(vibration.high_amplitude), .high_frequency = vibration.high_frequency, + .type = Input::VibrationAmplificationType::Exponential, }; + Input::VibrationStatus new_vibration{}; + + if (vibration.type == Input::VibrationAmplificationType::Linear || joystick->HasHDRumble()) { + new_vibration = vibration; + } else { + new_vibration = exponential_vibration; + } + if (!joystick->RumblePlay(new_vibration)) { return Input::VibrationError::Unknown; } + return Input::VibrationError::None; } Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, -- cgit v1.2.3 From 85052b8662d9512077780f717fb2e168390ed705 Mon Sep 17 00:00:00 2001 From: german77 Date: Wed, 20 Oct 2021 23:18:04 -0500 Subject: service/hid: Fix gesture input --- src/input_common/drivers/gc_adapter.cpp | 4 ++-- src/input_common/drivers/udp_client.cpp | 27 +++++++++++++++++++++++++++ 2 files changed, 29 insertions(+), 2 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 4fb6ab5af..25b66f528 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -209,7 +209,7 @@ void GCAdapter::UpdateStateAxes(std::size_t port, const AdapterPayload& adapter_ pads[port].axis_origin[index] = axis_value; pads[port].reset_origin_counter++; } - const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 110.0f; + const f32 axis_status = (axis_value - pads[port].axis_origin[index]) / 100.0f; SetAxis(pads[port].identifier, static_cast(index), axis_status); } } @@ -530,7 +530,7 @@ std::string GCAdapter::GetUIName(const Common::ParamPackage& params) const { return fmt::format("Button {}", GetUIButtonName(params)); } if (params.Has("axis")) { - return fmt::format("Axis {}", params.Get("axis",0)); + return fmt::format("Axis {}", params.Get("axis", 0)); } return "Bad GC Adapter"; diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index 6fcc3a01b..f0c0a6b8b 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -243,6 +243,33 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { }; 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 int touch_id = static_cast(client * 2 + id); + + // TODO: Use custom calibration per device + const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); + const u16 min_x = static_cast(touch_param.Get("min_x", 100)); + const u16 min_y = static_cast(touch_param.Get("min_y", 50)); + const u16 max_x = static_cast(touch_param.Get("max_x", 1800)); + const u16 max_y = static_cast(touch_param.Get("max_y", 850)); + + const f32 x = + static_cast(std::clamp(static_cast(touch_pad.x), min_x, max_x) - min_x) / + static_cast(max_x - min_x); + const f32 y = + static_cast(std::clamp(static_cast(touch_pad.y), min_y, max_y) - min_y) / + static_cast(max_y - min_y); + + if (touch_pad.is_active) { + SetAxis(identifier, touch_id * 2, x); + SetAxis(identifier, touch_id * 2 + 1, y); + SetButton(identifier, touch_id, true); + continue; + } + SetButton(identifier, touch_id, false); + } } void UDPClient::StartCommunication(std::size_t client, const std::string& host, u16 port) { -- cgit v1.2.3 From b5e72de753ae4de5c5fae7087abb00dc4242451d Mon Sep 17 00:00:00 2001 From: german77 Date: Thu, 21 Oct 2021 13:56:52 -0500 Subject: kraken: Address comments from review review fixes --- src/input_common/drivers/udp_client.cpp | 2 ++ src/input_common/drivers/udp_client.h | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index f0c0a6b8b..192ab336b 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -268,6 +268,8 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { SetButton(identifier, touch_id, true); continue; } + SetAxis(identifier, touch_id * 2, 0); + SetAxis(identifier, touch_id * 2 + 1, 0); SetButton(identifier, touch_id, false); } } diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h index 58b2e921d..639325b17 100644 --- a/src/input_common/drivers/udp_client.h +++ b/src/input_common/drivers/udp_client.h @@ -1,6 +1,6 @@ -// Copyright 2021 yuzu Emulator Project +// Copyright 2018 Citra Emulator Project // Licensed under GPLv2 or any later version -// Refer to the license.txt file included +// Refer to the license.txt file included. #pragma once -- cgit v1.2.3 From 464c4d26ac8e7af6302390684445b357e5cda4e4 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 24 Oct 2021 11:22:20 -0500 Subject: settings: Fix mouse and keyboard mappings --- src/input_common/drivers/mouse.cpp | 17 ++++++++++++++++- src/input_common/drivers/mouse.h | 1 + 2 files changed, 17 insertions(+), 1 deletion(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 2c2432fb7..1c32b54be 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -121,12 +121,27 @@ void Mouse::StopPanning() { std::vector Mouse::GetInputDevices() const { std::vector devices; devices.emplace_back(Common::ParamPackage{ - {"engine", "keyboard"}, + {"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; +} + std::string Mouse::GetUIName(const Common::ParamPackage& params) const { if (params.Has("button")) { return fmt::format("Mouse {}", params.Get("button", 0)); diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index e8355751a..d3178b1a9 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -55,6 +55,7 @@ public: void ReleaseAllButtons(); std::vector GetInputDevices() const override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; std::string GetUIName(const Common::ParamPackage& params) const override; private: -- cgit v1.2.3 From 7348e205d94e2fff777781498a2ef7931163703a Mon Sep 17 00:00:00 2001 From: german77 Date: Tue, 26 Oct 2021 23:37:46 -0500 Subject: input_common: Add multiple vibration curves --- src/input_common/drivers/sdl_driver.cpp | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index d56351815..53e282ef3 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -94,7 +94,6 @@ public: bool RumblePlay(const Input::VibrationStatus vibration) { constexpr u32 rumble_max_duration_ms = 1000; - if (sdl_controller) { return SDL_GameControllerRumble( sdl_controller.get(), static_cast(vibration.low_amplitude), @@ -520,25 +519,31 @@ Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, const Input::VibrationStatus vibration) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.Format(), static_cast(identifier.port)); - const auto process_amplitude = [](f32 amplitude) { - return (amplitude + std::pow(amplitude, 0.3f)) * 0.5f * 0xFFFF; + const auto process_amplitude_exp = [](f32 amplitude, f32 factor) { + return (amplitude + std::pow(amplitude, factor)) * 0.5f * 0xFFFF; }; - const Input::VibrationStatus exponential_vibration{ - .low_amplitude = process_amplitude(vibration.low_amplitude), + + // Default exponential curve for rumble + f32 factor = 0.35f; + + // If vibration is set as a linear output use a flatter value + if (vibration.type == Input::VibrationAmplificationType::Linear) { + factor = 0.5f; + } + + // Amplitude for HD rumble needs no modification + if (joystick->HasHDRumble()) { + factor = 1.0f; + } + + const Input::VibrationStatus new_vibration{ + .low_amplitude = process_amplitude_exp(vibration.low_amplitude, factor), .low_frequency = vibration.low_frequency, - .high_amplitude = process_amplitude(vibration.high_amplitude), + .high_amplitude = process_amplitude_exp(vibration.high_amplitude, factor), .high_frequency = vibration.high_frequency, .type = Input::VibrationAmplificationType::Exponential, }; - Input::VibrationStatus new_vibration{}; - - if (vibration.type == Input::VibrationAmplificationType::Linear || joystick->HasHDRumble()) { - new_vibration = vibration; - } else { - new_vibration = exponential_vibration; - } - if (!joystick->RumblePlay(new_vibration)) { return Input::VibrationError::Unknown; } -- cgit v1.2.3 From 1d71d4b87415b2581e6757f07d29275e02671b7a Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 25 Oct 2021 12:53:14 -0500 Subject: input_common: Fix UDP uuid --- src/input_common/drivers/udp_client.cpp | 10 +++++++++- src/input_common/drivers/udp_client.h | 2 ++ 2 files changed, 11 insertions(+), 1 deletion(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index 192ab336b..7cab707da 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -4,6 +4,7 @@ #include #include +#include #include "common/logging/log.h" #include "common/param_package.h" @@ -279,6 +280,7 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host, [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; @@ -293,12 +295,18 @@ void UDPClient::StartCommunication(std::size_t client, const std::string& host, const PadIdentifier UDPClient::GetPadIdentifier(std::size_t pad_index) const { const std::size_t client = pad_index / PADS_PER_CLIENT; return { - .guid = Common::UUID{clients[client].host}, + .guid = clients[client].uuid, .port = static_cast(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()) { diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h index 639325b17..1f02adba5 100644 --- a/src/input_common/drivers/udp_client.h +++ b/src/input_common/drivers/udp_client.h @@ -69,6 +69,7 @@ private: struct ClientConnection { ClientConnection(); ~ClientConnection(); + Common::UUID uuid{"7F000001"}; std::string host{"127.0.0.1"}; u16 port{26760}; s8 active{-1}; @@ -87,6 +88,7 @@ private: void OnPadData(Response::PadData, std::size_t client); void StartCommunication(std::size_t client, const std::string& host, u16 port); const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; + const Common::UUID GetHostUUID(const std::string host) const; // Allocate clients for 8 udp servers static constexpr std::size_t MAX_UDP_CLIENTS = 8; -- cgit v1.2.3 From d8e3f2b10bad7e4a0ae09c90c76e8a1927c3d1ab Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 30 Oct 2021 11:20:47 -0500 Subject: input_common: Fix GC adapter initialization Fix GC controller --- src/input_common/drivers/gc_adapter.cpp | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 25b66f528..62dc28711 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -73,7 +73,7 @@ GCAdapter::GCAdapter(const std::string input_engine_) : InputEngine(input_engine if (usb_adapter_handle) { return; } - LOG_INFO(Input, "GC Adapter Initialization started"); + LOG_DEBUG(Input, "Initialization started"); libusb_ctx = std::make_unique(); const int init_res = libusb_ctx->InitResult(); @@ -90,7 +90,7 @@ GCAdapter::~GCAdapter() { } void GCAdapter::AdapterInputThread(std::stop_token stop_token) { - LOG_DEBUG(Input, "GC Adapter input thread started"); + LOG_DEBUG(Input, "Input thread started"); Common::SetCurrentThreadName("yuzu:input:GCAdapter"); s32 payload_size{}; AdapterPayload adapter_payload{}; @@ -120,7 +120,7 @@ bool GCAdapter::IsPayloadCorrect(const AdapterPayload& adapter_payload, s32 payl 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?"); + LOG_ERROR(Input, "Timeout, Is the adapter connected?"); adapter_input_thread.request_stop(); restart_scan_thread = true; } @@ -263,13 +263,6 @@ bool GCAdapter::Setup() { } bool GCAdapter::CheckDeviceAccess() { - // 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); - } - 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->get(), 0); @@ -291,6 +284,13 @@ bool GCAdapter::CheckDeviceAccess() { 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; } @@ -370,9 +370,9 @@ void GCAdapter::SendVibrations() { libusb_interrupt_transfer(usb_adapter_handle->get(), output_endpoint, payload.data(), static_cast(payload.size()), &size, 16); if (err) { - LOG_DEBUG(Input, "Adapter libusb write failed: {}", libusb_error_name(err)); + LOG_DEBUG(Input, "Libusb write failed: {}", libusb_error_name(err)); if (output_error_counter++ > 5) { - LOG_ERROR(Input, "GC adapter output timeout, Rumble disabled"); + LOG_ERROR(Input, "Output timeout, Rumble disabled"); rumble_enabled = false; } return; -- cgit v1.2.3 From 61d9eb9f690d6afe141f24ba75c99b54e122dfa3 Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 30 Oct 2021 20:16:10 -0500 Subject: input_common: Revert deleted TAS functions --- src/input_common/drivers/tas_input.cpp | 23 ++++++++++++----------- src/input_common/drivers/tas_input.h | 14 +++++++------- 2 files changed, 19 insertions(+), 18 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index 7e7a1d58f..d2748b240 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp @@ -141,7 +141,7 @@ void Tas::WriteTasFile(std::u8string file_name) { } } -void Tas::RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis) { +void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) { last_input = { .buttons = buttons, .l_axis = FlipAxisY(left_axis), @@ -195,7 +195,7 @@ void Tas::UpdateThread() { } if (current_command < script_length) { LOG_DEBUG(Input, "Playing TAS {}/{}", current_command, script_length); - size_t frame = current_command++; + 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()) { @@ -207,8 +207,8 @@ void Tas::UpdateThread() { .port = player_index, .pad = 0, }; - for (std::size_t i = 0; i < sizeof(command.buttons); ++i) { - const bool button_status = (command.buttons & (1U << i)) != 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(i); SetButton(identifier, button, button_status); } @@ -244,14 +244,14 @@ TasAnalog Tas::ReadCommandAxis(const std::string& line) const { return {x, y}; } -u32 Tas::ReadCommandButtons(const std::string& data) const { +u64 Tas::ReadCommandButtons(const std::string& data) const { std::stringstream button_text(data); std::string line; - u32 buttons = 0; + u64 buttons = 0; while (std::getline(button_text, line, ';')) { for (auto [text, tas_button] : text_to_tas_button) { if (text == line) { - buttons |= static_cast(tas_button); + buttons |= static_cast(tas_button); break; } } @@ -259,13 +259,14 @@ u32 Tas::ReadCommandButtons(const std::string& data) const { return buttons; } -std::string Tas::WriteCommandButtons(u32 buttons) const { +std::string Tas::WriteCommandButtons(u64 buttons) const { std::string returns = ""; for (auto [text_button, tas_button] : text_to_tas_button) { - if ((buttons & static_cast(tas_button)) != 0) - returns += fmt::format("{};", text_button.substr(4)); + if ((buttons & static_cast(tas_button)) != 0) { + returns += fmt::format("{};", text_button); + } } - return returns.empty() ? "NONE" : returns.substr(2); + return returns.empty() ? "NONE" : returns; } std::string Tas::WriteCommandAxis(TasAnalog analog) const { diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h index 9fadc118b..5f5c3267c 100644 --- a/src/input_common/drivers/tas_input.h +++ b/src/input_common/drivers/tas_input.h @@ -47,7 +47,7 @@ namespace InputCommon::TasInput { constexpr size_t PLAYER_NUMBER = 10; -enum class TasButton : u32 { +enum class TasButton : u64 { BUTTON_A = 1U << 0, BUTTON_B = 1U << 1, BUTTON_X = 1U << 2, @@ -92,7 +92,7 @@ public: * @param left_axis: value of the left axis * @param right_axis: value of the right axis */ - void RecordInput(u32 buttons, TasAnalog left_axis, TasAnalog right_axis); + void RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis); // Main loop that records or executes input void UpdateThread(); @@ -129,7 +129,7 @@ public: private: struct TASCommand { - u32 buttons{}; + u64 buttons{}; TasAnalog l_axis{}; TasAnalog r_axis{}; }; @@ -164,9 +164,9 @@ private: * 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..." - * @return Returns a u32 with each bit representing the status of a button + * @return Returns a u64 with each bit representing the status of a button */ - u32 ReadCommandButtons(const std::string& line) const; + u64 ReadCommandButtons(const std::string& line) const; /** * Reset state of all players @@ -174,11 +174,11 @@ private: void ClearInput(); /** - * Converts an u32 containing the button status into the text equivalent + * Converts an u64 containing the button status into the text equivalent * @param buttons: bitfield with the status of the buttons * @return Returns a string with the name of the buttons to be written to the file */ - std::string WriteCommandButtons(u32 buttons) const; + std::string WriteCommandButtons(u64 buttons) const; /** * Converts an TAS analog object containing the axis status into the text equivalent -- cgit v1.2.3 From 2b1b0c2a30e242b08ec120e09803ec54d5445703 Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 30 Oct 2021 22:23:10 -0500 Subject: kraken: Address comments from review start lion review --- src/input_common/drivers/gc_adapter.cpp | 10 +++++----- src/input_common/drivers/gc_adapter.h | 7 +++++-- src/input_common/drivers/keyboard.h | 2 +- src/input_common/drivers/mouse.h | 2 +- src/input_common/drivers/sdl_driver.cpp | 16 ++++++++-------- src/input_common/drivers/sdl_driver.h | 7 ++----- src/input_common/drivers/touch_screen.h | 2 +- 7 files changed, 23 insertions(+), 23 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 62dc28711..2550f8cba 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -248,7 +248,7 @@ bool GCAdapter::Setup() { std::size_t port = 0; for (GCController& pad : pads) { pad.identifier = { - .guid = Common::UUID{""}, + .guid = Common::UUID{Common::INVALID_UUID}, .port = port++, .pad = 0, }; @@ -325,8 +325,8 @@ bool GCAdapter::GetGCEndpoint(libusb_device* device) { return true; } -Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, - const Input::VibrationStatus vibration) { +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((mean_amplitude + std::pow(mean_amplitude, 0.3f)) * 0.5f * 0x8); @@ -334,9 +334,9 @@ Input::VibrationError GCAdapter::SetRumble(const PadIdentifier& identifier, pads[identifier.port].rumble_amplitude = processed_amplitude; if (!rumble_enabled) { - return Input::VibrationError::Disabled; + return Common::Input::VibrationError::Disabled; } - return Input::VibrationError::None; + return Common::Input::VibrationError::None; } void GCAdapter::UpdateVibrations() { diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index b82e4803d..fba90352e 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -4,8 +4,11 @@ #pragma once +#include +#include #include #include +#include #include #include "input_common/input_engine.h" @@ -24,8 +27,8 @@ public: explicit GCAdapter(const std::string input_engine_); ~GCAdapter(); - Input::VibrationError SetRumble(const PadIdentifier& identifier, - const Input::VibrationStatus vibration) override; + Common::Input::VibrationError SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override; /// Used for automapping features std::vector GetInputDevices() const override; diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h index a3e0d8a61..58df15050 100644 --- a/src/input_common/drivers/keyboard.h +++ b/src/input_common/drivers/keyboard.h @@ -35,7 +35,7 @@ public: private: const PadIdentifier identifier = { - .guid = Common::UUID{""}, + .guid = Common::UUID{Common::INVALID_UUID}, .port = 0, .pad = 0, }; diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index d3178b1a9..cf0918409 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -63,7 +63,7 @@ private: void StopPanning(); const PadIdentifier identifier = { - .guid = Common::UUID{""}, + .guid = Common::UUID{Common::INVALID_UUID}, .port = 0, .pad = 0, }; diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 53e282ef3..1e3741e0f 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -92,7 +92,7 @@ public: return motion; } - bool RumblePlay(const Input::VibrationStatus vibration) { + bool RumblePlay(const Common::Input::VibrationStatus vibration) { constexpr u32 rumble_max_duration_ms = 1000; if (sdl_controller) { return SDL_GameControllerRumble( @@ -515,8 +515,8 @@ std::vector SDLDriver::GetInputDevices() const { } return devices; } -Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, - const Input::VibrationStatus vibration) { +Common::Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, + const Common::Input::VibrationStatus vibration) { const auto joystick = GetSDLJoystickByGUID(identifier.guid.Format(), static_cast(identifier.port)); const auto process_amplitude_exp = [](f32 amplitude, f32 factor) { @@ -527,7 +527,7 @@ Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, f32 factor = 0.35f; // If vibration is set as a linear output use a flatter value - if (vibration.type == Input::VibrationAmplificationType::Linear) { + if (vibration.type == Common::Input::VibrationAmplificationType::Linear) { factor = 0.5f; } @@ -536,19 +536,19 @@ Input::VibrationError SDLDriver::SetRumble(const PadIdentifier& identifier, factor = 1.0f; } - const Input::VibrationStatus new_vibration{ + 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 = Input::VibrationAmplificationType::Exponential, + .type = Common::Input::VibrationAmplificationType::Exponential, }; if (!joystick->RumblePlay(new_vibration)) { - return Input::VibrationError::Unknown; + return Common::Input::VibrationError::Unknown; } - return Input::VibrationError::None; + return Common::Input::VibrationError::None; } Common::ParamPackage SDLDriver::BuildAnalogParamPackageForButton(int port, std::string guid, s32 axis, float value) const { diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 1ff85f48d..b879df8ab 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -58,8 +58,8 @@ public: std::string GetHatButtonName(u8 direction_value) const override; u8 GetHatButtonId(const std::string direction_name) const override; - Input::VibrationError SetRumble(const PadIdentifier& identifier, - const Input::VibrationStatus vibration) override; + Common::Input::VibrationError SetRumble( + const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override; private: void InitJoystick(int joystick_index); @@ -105,9 +105,6 @@ 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>> joystick_map; std::mutex joystick_map_mutex; diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h index 5fbb2f47f..d297d253c 100644 --- a/src/input_common/drivers/touch_screen.h +++ b/src/input_common/drivers/touch_screen.h @@ -41,7 +41,7 @@ public: private: const PadIdentifier identifier = { - .guid = Common::UUID{""}, + .guid = Common::UUID{Common::INVALID_UUID}, .port = 0, .pad = 0, }; -- cgit v1.2.3 From 730f07830247cfcdc551c253d30c6717fc16316c Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 31 Oct 2021 10:41:44 -0500 Subject: settings: Fix Debug controller type options --- src/input_common/drivers/tas_input.h | 1 - 1 file changed, 1 deletion(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h index 5f5c3267c..82dc9d616 100644 --- a/src/input_common/drivers/tas_input.h +++ b/src/input_common/drivers/tas_input.h @@ -188,7 +188,6 @@ private: std::string WriteCommandAxis(TasAnalog data) const; size_t script_length{0}; - bool is_old_input_saved{false}; bool is_recording{false}; bool is_running{false}; bool needs_reset{false}; -- cgit v1.2.3 From 77fa4d4bf60526826ef8b53ee3870f7d2a761976 Mon Sep 17 00:00:00 2001 From: german77 Date: Mon, 1 Nov 2021 14:17:53 -0600 Subject: second commit lion review --- src/input_common/drivers/keyboard.cpp | 6 ++++++ src/input_common/drivers/keyboard.h | 7 ------- src/input_common/drivers/mouse.cpp | 5 +++++ src/input_common/drivers/mouse.h | 5 ----- src/input_common/drivers/touch_screen.cpp | 6 ++++++ src/input_common/drivers/touch_screen.h | 8 +------- 6 files changed, 18 insertions(+), 19 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp index 85a781a30..549704e89 100644 --- a/src/input_common/drivers/keyboard.cpp +++ b/src/input_common/drivers/keyboard.cpp @@ -7,6 +7,12 @@ namespace InputCommon { +constexpr PadIdentifier identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; + Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) { PreSetController(identifier); } diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h index 58df15050..46fe78576 100644 --- a/src/input_common/drivers/keyboard.h +++ b/src/input_common/drivers/keyboard.h @@ -32,13 +32,6 @@ public: /// Used for automapping features std::vector GetInputDevices() const override; - -private: - const PadIdentifier identifier = { - .guid = Common::UUID{Common::INVALID_UUID}, - .port = 0, - .pad = 0, - }; }; } // namespace InputCommon diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 1c32b54be..afa92b458 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -14,6 +14,11 @@ namespace InputCommon { 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(const std::string input_engine_) : InputEngine(input_engine_) { PreSetController(identifier); diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index cf0918409..1be362b94 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -62,11 +62,6 @@ private: void UpdateThread(std::stop_token stop_token); void StopPanning(); - const PadIdentifier identifier = { - .guid = Common::UUID{Common::INVALID_UUID}, - .port = 0, - .pad = 0, - }; Common::Vec2 mouse_origin; Common::Vec2 last_mouse_position; Common::Vec2 last_mouse_change; diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp index e13835e9f..377c9ee2b 100644 --- a/src/input_common/drivers/touch_screen.cpp +++ b/src/input_common/drivers/touch_screen.cpp @@ -7,6 +7,12 @@ namespace InputCommon { +constexpr PadIdentifier identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 0, +}; + TouchScreen::TouchScreen(const std::string input_engine_) : InputEngine(input_engine_) { PreSetController(identifier); } diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h index d297d253c..0f4cd0e7a 100644 --- a/src/input_common/drivers/touch_screen.h +++ b/src/input_common/drivers/touch_screen.h @@ -37,14 +37,8 @@ public: */ void TouchReleased(std::size_t finger); + /// Resets all inputs to their initial value void ReleaseAllTouch(); - -private: - const PadIdentifier identifier = { - .guid = Common::UUID{Common::INVALID_UUID}, - .port = 0, - .pad = 0, - }; }; } // namespace InputCommon -- cgit v1.2.3 From 157e0b85fdd805e02d234dccf1ce578e3159adee Mon Sep 17 00:00:00 2001 From: german77 Date: Tue, 2 Nov 2021 22:50:30 -0600 Subject: core/hid: Prevent Emulated controller from flapping with multiple inputs devices --- src/input_common/drivers/tas_input.cpp | 24 +++++++++--------------- src/input_common/drivers/tas_input.h | 11 +++-------- 2 files changed, 12 insertions(+), 23 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index d2748b240..bb9c236ea 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp @@ -71,21 +71,21 @@ Tas::~Tas() { void Tas::LoadTasFiles() { script_length = 0; for (size_t i = 0; i < commands.size(); i++) { - LoadTasFile(i); + LoadTasFile(i, 0); if (commands[i].size() > script_length) { script_length = commands[i].size(); } } } -void Tas::LoadTasFile(size_t player_index) { +void Tas::LoadTasFile(size_t player_index, size_t file_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::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::stringstream command_line(file); std::string line; int frame_no = 0; @@ -144,15 +144,8 @@ void Tas::WriteTasFile(std::u8string file_name) { void Tas::RecordInput(u64 buttons, TasAnalog left_axis, TasAnalog right_axis) { last_input = { .buttons = buttons, - .l_axis = FlipAxisY(left_axis), - .r_axis = FlipAxisY(right_axis), - }; -} - -TasAnalog Tas::FlipAxisY(TasAnalog old) { - return { - .x = old.x, - .y = -old.y, + .l_axis = left_axis, + .r_axis = right_axis, }; } @@ -219,6 +212,7 @@ void Tas::UpdateThread() { } } else { is_running = Settings::values.tas_loop.GetValue(); + LoadTasFiles(); current_command = 0; ClearInput(); } diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h index 82dc9d616..bfb37a638 100644 --- a/src/input_common/drivers/tas_input.h +++ b/src/input_common/drivers/tas_input.h @@ -138,21 +138,16 @@ private: void LoadTasFiles(); /** Loads TAS file from the specified player - * @param player_index: player number where data is going to be stored + * @param player_index: player number to save the script + * @param file_index: script number of the file */ - void LoadTasFile(size_t player_index); + 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 file_name); - /** Inverts the Y axis polarity - * @param old: value of the axis - * @return new value of the axis - */ - TasAnalog FlipAxisY(TasAnalog old); - /** * 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" -- cgit v1.2.3 From b673857d7dfc72f38d9242b315cd590b859795ff Mon Sep 17 00:00:00 2001 From: german77 Date: Sat, 13 Nov 2021 23:25:45 -0600 Subject: core/hid: Improve accuracy of the keyboard implementation --- src/input_common/drivers/keyboard.cpp | 53 ++++++++++++++++++++++++++++++++--- src/input_common/drivers/keyboard.h | 7 +++++ 2 files changed, 56 insertions(+), 4 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp index 549704e89..328fe1ac1 100644 --- a/src/input_common/drivers/keyboard.cpp +++ b/src/input_common/drivers/keyboard.cpp @@ -3,26 +3,71 @@ // 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 identifier = { +constexpr PadIdentifier key_identifier = { .guid = Common::UUID{Common::INVALID_UUID}, .port = 0, .pad = 0, }; +constexpr PadIdentifier modifier_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 0, + .pad = 1, +}; Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(input_engine_) { - PreSetController(identifier); + PreSetController(key_identifier); + PreSetController(modifier_identifier); } void Keyboard::PressKey(int key_code) { - SetButton(identifier, key_code, true); + SetButton(key_identifier, key_code, true); } void Keyboard::ReleaseKey(int key_code) { - SetButton(identifier, key_code, false); + SetButton(key_identifier, key_code, false); +} + +void Keyboard::SetModifiers(int key_modifiers) { + for (int i = 0; i < 32; ++i) { + bool key_value = ((key_modifiers >> i) & 0x1) != 0; + SetButton(modifier_identifier, i, key_value); + // Use the modifier to press the key button equivalent + switch (i) { + case Settings::NativeKeyboard::LeftControl: + SetButton(key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value); + break; + case Settings::NativeKeyboard::LeftShift: + SetButton(key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value); + break; + case Settings::NativeKeyboard::LeftAlt: + SetButton(key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value); + break; + case Settings::NativeKeyboard::LeftMeta: + SetButton(key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value); + break; + case Settings::NativeKeyboard::RightControl: + SetButton(key_identifier, Settings::NativeKeyboard::RightControlKey, key_value); + break; + case Settings::NativeKeyboard::RightShift: + SetButton(key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value); + break; + case Settings::NativeKeyboard::RightAlt: + SetButton(key_identifier, Settings::NativeKeyboard::RightAltKey, key_value); + break; + case Settings::NativeKeyboard::RightMeta: + SetButton(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() { diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h index 46fe78576..2ab92fd6c 100644 --- a/src/input_common/drivers/keyboard.h +++ b/src/input_common/drivers/keyboard.h @@ -28,6 +28,13 @@ public: */ void ReleaseKey(int key_code); + /** + * Sets the status of all keyboard modifier keys + * @param key_modifiers the code of the key to release + */ + void SetModifiers(int key_modifiers); + + /// Sets all keys to the non pressed state void ReleaseAllKeys(); /// Used for automapping features -- cgit v1.2.3 From bca299e8e0489867f7d4bbfd264e221e7e61ae1e Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 14 Nov 2021 10:45:07 -0600 Subject: input_common: Allow keyboard to be backwards compatible --- src/input_common/drivers/keyboard.cpp | 52 ++++++++++++++++++++++++++--------- src/input_common/drivers/keyboard.h | 14 +++++++++- 2 files changed, 52 insertions(+), 14 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/keyboard.cpp b/src/input_common/drivers/keyboard.cpp index 328fe1ac1..23b0c0ccf 100644 --- a/src/input_common/drivers/keyboard.cpp +++ b/src/input_common/drivers/keyboard.cpp @@ -13,15 +13,26 @@ constexpr PadIdentifier key_identifier = { .port = 0, .pad = 0, }; -constexpr PadIdentifier modifier_identifier = { +constexpr PadIdentifier keyboard_key_identifier = { .guid = Common::UUID{Common::INVALID_UUID}, - .port = 0, + .port = 1, + .pad = 0, +}; +constexpr PadIdentifier keyboard_modifier_identifier = { + .guid = Common::UUID{Common::INVALID_UUID}, + .port = 1, .pad = 1, }; Keyboard::Keyboard(const std::string& input_engine_) : InputEngine(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(modifier_identifier); + PreSetController(keyboard_key_identifier); + PreSetController(keyboard_modifier_identifier); } void Keyboard::PressKey(int key_code) { @@ -32,35 +43,50 @@ void Keyboard::ReleaseKey(int key_code) { SetButton(key_identifier, key_code, false); } -void Keyboard::SetModifiers(int key_modifiers) { +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(modifier_identifier, i, key_value); + SetButton(keyboard_modifier_identifier, i, key_value); // Use the modifier to press the key button equivalent switch (i) { case Settings::NativeKeyboard::LeftControl: - SetButton(key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftControlKey, key_value); break; case Settings::NativeKeyboard::LeftShift: - SetButton(key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftShiftKey, key_value); break; case Settings::NativeKeyboard::LeftAlt: - SetButton(key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftAltKey, key_value); break; case Settings::NativeKeyboard::LeftMeta: - SetButton(key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::LeftMetaKey, key_value); break; case Settings::NativeKeyboard::RightControl: - SetButton(key_identifier, Settings::NativeKeyboard::RightControlKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightControlKey, + key_value); break; case Settings::NativeKeyboard::RightShift: - SetButton(key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightShiftKey, key_value); break; case Settings::NativeKeyboard::RightAlt: - SetButton(key_identifier, Settings::NativeKeyboard::RightAltKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightAltKey, key_value); break; case Settings::NativeKeyboard::RightMeta: - SetButton(key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value); + SetButton(keyboard_key_identifier, Settings::NativeKeyboard::RightMetaKey, key_value); break; default: // Other modifier keys should be pressed with PressKey since they stay enabled until diff --git a/src/input_common/drivers/keyboard.h b/src/input_common/drivers/keyboard.h index 2ab92fd6c..ad123b136 100644 --- a/src/input_common/drivers/keyboard.h +++ b/src/input_common/drivers/keyboard.h @@ -28,11 +28,23 @@ public: */ 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 SetModifiers(int key_modifiers); + void SetKeyboardModifiers(int key_modifiers); /// Sets all keys to the non pressed state void ReleaseAllKeys(); -- cgit v1.2.3 From 654d76e79e84a3384fa503fac9003a5d0a32f28b Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 14 Nov 2021 14:09:29 -0600 Subject: core/hid: Fully implement native mouse --- src/input_common/drivers/mouse.cpp | 31 +++++++++++++++++++++++++------ src/input_common/drivers/mouse.h | 7 +++++++ 2 files changed, 32 insertions(+), 6 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index afa92b458..478737db2 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -12,6 +12,10 @@ #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 = { @@ -22,6 +26,12 @@ constexpr PadIdentifier identifier = { Mouse::Mouse(const std::string input_engine_) : InputEngine(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); }); } @@ -34,14 +44,18 @@ void Mouse::UpdateThread(std::stop_token stop_token) { last_mouse_change *= 0.96f; const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.022f; - SetAxis(identifier, 0, last_mouse_change.x * sensitivity); - SetAxis(identifier, 1, -last_mouse_change.y * sensitivity); + 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)); + + // Reset wheel position + SetAxis(identifier, wheel_axis_x, 0); + SetAxis(identifier, wheel_axis_y, 0); } } @@ -89,8 +103,8 @@ void Mouse::MouseMove(int x, int y, f32 touch_x, f32 touch_y, int center_x, int if (button_pressed) { const auto mouse_move = Common::MakeVec(x, y) - mouse_origin; const float sensitivity = Settings::values.mouse_panning_sensitivity.GetValue() * 0.0012f; - SetAxis(identifier, 0, static_cast(mouse_move.x) * sensitivity); - SetAxis(identifier, 1, static_cast(-mouse_move.y) * sensitivity); + SetAxis(identifier, mouse_axis_x, static_cast(mouse_move.x) * sensitivity); + SetAxis(identifier, mouse_axis_y, static_cast(-mouse_move.y) * sensitivity); } } @@ -108,12 +122,17 @@ void Mouse::ReleaseButton(MouseButton button) { SetButton(identifier, static_cast(button), false); if (!Settings::values.mouse_panning) { - SetAxis(identifier, 0, 0); - SetAxis(identifier, 1, 0); + SetAxis(identifier, mouse_axis_x, 0); + SetAxis(identifier, mouse_axis_y, 0); } button_pressed = false; } +void Mouse::MouseWheelChange(int x, int y) { + SetAxis(identifier, wheel_axis_x, static_cast(x)); + SetAxis(identifier, wheel_axis_y, static_cast(y)); +} + void Mouse::ReleaseAllButtons() { ResetButtonState(); button_pressed = false; diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 1be362b94..429502af9 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -52,6 +52,13 @@ public: */ 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 GetInputDevices() const override; -- cgit v1.2.3 From f4e5f89e6fb9d68cd4ba7d98c281584c50f0e149 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 14 Nov 2021 21:28:38 -0600 Subject: core/hid: Improve accuary of mouse implementation --- src/input_common/drivers/mouse.cpp | 21 +++++++++++++-------- src/input_common/drivers/mouse.h | 1 + 2 files changed, 14 insertions(+), 8 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 478737db2..05fd7f9c0 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -39,7 +39,7 @@ 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) { + if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) { // Slow movement by 4% last_mouse_change *= 0.96f; const float sensitivity = @@ -52,14 +52,17 @@ void Mouse::UpdateThread(std::stop_token stop_token) { StopPanning(); } std::this_thread::sleep_for(std::chrono::milliseconds(update_time)); - - // Reset wheel position - SetAxis(identifier, wheel_axis_x, 0); - SetAxis(identifier, wheel_axis_y, 0); } } 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); @@ -121,7 +124,7 @@ void Mouse::PressButton(int x, int y, f32 touch_x, f32 touch_y, MouseButton butt void Mouse::ReleaseButton(MouseButton button) { SetButton(identifier, static_cast(button), false); - if (!Settings::values.mouse_panning) { + if (!Settings::values.mouse_panning && !Settings::values.mouse_enabled) { SetAxis(identifier, mouse_axis_x, 0); SetAxis(identifier, mouse_axis_y, 0); } @@ -129,8 +132,10 @@ void Mouse::ReleaseButton(MouseButton button) { } void Mouse::MouseWheelChange(int x, int y) { - SetAxis(identifier, wheel_axis_x, static_cast(x)); - SetAxis(identifier, wheel_axis_y, static_cast(y)); + wheel_position.x += x; + wheel_position.y += y; + SetAxis(identifier, wheel_axis_x, static_cast(wheel_position.x)); + SetAxis(identifier, wheel_axis_y, static_cast(wheel_position.y)); } void Mouse::ReleaseAllButtons() { diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 429502af9..f7e6db0b5 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -72,6 +72,7 @@ private: Common::Vec2 mouse_origin; Common::Vec2 last_mouse_position; Common::Vec2 last_mouse_change; + Common::Vec2 wheel_position; bool button_pressed; int mouse_panning_timout{}; std::jthread update_thread; -- cgit v1.2.3 From 42949738f2c01a4125a9a385c9100240181153ec Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 14 Nov 2021 21:56:54 -0600 Subject: kraken: Address comments from review Fix compiler bug --- src/input_common/drivers/gc_adapter.cpp | 16 ++-------------- src/input_common/drivers/gc_adapter.h | 2 +- src/input_common/drivers/mouse.cpp | 2 +- src/input_common/drivers/mouse.h | 2 +- src/input_common/drivers/sdl_driver.cpp | 2 +- src/input_common/drivers/sdl_driver.h | 2 +- src/input_common/drivers/tas_input.cpp | 2 +- src/input_common/drivers/tas_input.h | 2 +- src/input_common/drivers/touch_screen.cpp | 2 +- src/input_common/drivers/touch_screen.h | 2 +- 10 files changed, 11 insertions(+), 23 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index 2550f8cba..a1b9b6d98 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -69,7 +69,7 @@ private: libusb_device_handle* handle{}; }; -GCAdapter::GCAdapter(const std::string input_engine_) : InputEngine(input_engine_) { +GCAdapter::GCAdapter(const std::string& input_engine_) : InputEngine(input_engine_) { if (usb_adapter_handle) { return; } @@ -486,42 +486,30 @@ std::string GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const switch (button) { case PadButton::ButtonLeft: return "left"; - break; case PadButton::ButtonRight: return "right"; - break; case PadButton::ButtonDown: return "down"; - break; case PadButton::ButtonUp: return "up"; - break; case PadButton::TriggerZ: return "Z"; - break; case PadButton::TriggerR: return "R"; - break; case PadButton::TriggerL: return "L"; - break; case PadButton::ButtonA: return "A"; - break; case PadButton::ButtonB: return "B"; - break; case PadButton::ButtonX: return "X"; - break; case PadButton::ButtonY: return "Y"; - break; case PadButton::ButtonStart: return "start"; - break; default: - return "Unkown GC"; + return "Unknown GC"; } } diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index fba90352e..3e4747040 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -24,7 +24,7 @@ class LibUSBDeviceHandle; class GCAdapter : public InputCommon::InputEngine { public: - explicit GCAdapter(const std::string input_engine_); + explicit GCAdapter(const std::string& input_engine_); ~GCAdapter(); Common::Input::VibrationError SetRumble( diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 05fd7f9c0..9a9a1987d 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -24,7 +24,7 @@ constexpr PadIdentifier identifier = { .pad = 0, }; -Mouse::Mouse(const std::string input_engine_) : InputEngine(input_engine_) { +Mouse::Mouse(const std::string& input_engine_) : InputEngine(input_engine_) { PreSetController(identifier); PreSetAxis(identifier, mouse_axis_x); PreSetAxis(identifier, mouse_axis_y); diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index f7e6db0b5..11dd76e14 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -29,7 +29,7 @@ enum class MouseButton { */ class Mouse final : public InputCommon::InputEngine { public: - explicit Mouse(const std::string input_engine_); + explicit Mouse(const std::string& input_engine_); /** * Signals that mouse has moved. diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 1e3741e0f..0b24f1858 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -929,7 +929,7 @@ std::string SDLDriver::GetHatButtonName(u8 direction_value) const { } } -u8 SDLDriver::GetHatButtonId(const std::string direction_name) const { +u8 SDLDriver::GetHatButtonId(const std::string& direction_name) const { Uint8 direction; if (direction_name == "up") { direction = SDL_HAT_UP; diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index b879df8ab..3faaca984 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -56,7 +56,7 @@ public: std::string GetUIName(const Common::ParamPackage& params) const override; std::string GetHatButtonName(u8 direction_value) const override; - u8 GetHatButtonId(const std::string direction_name) const override; + u8 GetHatButtonId(const std::string& direction_name) const override; Common::Input::VibrationError SetRumble( const PadIdentifier& identifier, const Common::Input::VibrationStatus vibration) override; diff --git a/src/input_common/drivers/tas_input.cpp b/src/input_common/drivers/tas_input.cpp index bb9c236ea..0e01fb0d9 100644 --- a/src/input_common/drivers/tas_input.cpp +++ b/src/input_common/drivers/tas_input.cpp @@ -47,7 +47,7 @@ constexpr std::array, 20> text_to_tas_but {"KEY_ZR", TasButton::TRIGGER_ZR}, }; -Tas::Tas(const std::string input_engine_) : InputCommon::InputEngine(input_engine_) { +Tas::Tas(const std::string& input_engine_) : InputCommon::InputEngine(input_engine_) { for (size_t player_index = 0; player_index < PLAYER_NUMBER; player_index++) { PadIdentifier identifier{ .guid = Common::UUID{}, diff --git a/src/input_common/drivers/tas_input.h b/src/input_common/drivers/tas_input.h index bfb37a638..c95a130fc 100644 --- a/src/input_common/drivers/tas_input.h +++ b/src/input_common/drivers/tas_input.h @@ -83,7 +83,7 @@ enum class TasState { class Tas final : public InputCommon::InputEngine { public: - explicit Tas(const std::string input_engine_); + explicit Tas(const std::string& input_engine_); ~Tas(); /** diff --git a/src/input_common/drivers/touch_screen.cpp b/src/input_common/drivers/touch_screen.cpp index 377c9ee2b..45b3086f6 100644 --- a/src/input_common/drivers/touch_screen.cpp +++ b/src/input_common/drivers/touch_screen.cpp @@ -13,7 +13,7 @@ constexpr PadIdentifier identifier = { .pad = 0, }; -TouchScreen::TouchScreen(const std::string input_engine_) : InputEngine(input_engine_) { +TouchScreen::TouchScreen(const std::string& input_engine_) : InputEngine(input_engine_) { PreSetController(identifier); } diff --git a/src/input_common/drivers/touch_screen.h b/src/input_common/drivers/touch_screen.h index 0f4cd0e7a..25c11e8bf 100644 --- a/src/input_common/drivers/touch_screen.h +++ b/src/input_common/drivers/touch_screen.h @@ -14,7 +14,7 @@ namespace InputCommon { */ class TouchScreen final : public InputCommon::InputEngine { public: - explicit TouchScreen(const std::string input_engine_); + explicit TouchScreen(const std::string& input_engine_); /** * Signals that mouse has moved. -- cgit v1.2.3 From c4760489a0386cdeaed68ecbed7f87532193743e Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 21 Nov 2021 12:59:51 -0600 Subject: input_common: Fix SDL controller with inverted axis --- src/input_common/drivers/sdl_driver.cpp | 23 ----------------------- 1 file changed, 23 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index 0b24f1858..d5af6c09b 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -220,24 +220,6 @@ public: return "Unknown"; } - bool IsYAxis(u8 index) { - if (!sdl_controller) { - return false; - } - - const auto& binding_left_y = - SDL_GameControllerGetBindForAxis(sdl_controller.get(), SDL_CONTROLLER_AXIS_LEFTY); - const auto& binding_right_y = - SDL_GameControllerGetBindForAxis(sdl_controller.get(), SDL_CONTROLLER_AXIS_RIGHTY); - if (index == binding_left_y.value.axis) { - return true; - } - if (index == binding_right_y.value.axis) { - return true; - } - return false; - } - private: std::string guid; int port; @@ -376,11 +358,6 @@ void SDLDriver::HandleGameControllerEvent(const SDL_Event& event) { case SDL_JOYAXISMOTION: { if (const auto joystick = GetSDLJoystickBySDLID(event.jaxis.which)) { const PadIdentifier identifier = joystick->GetPadIdentifier(); - // Vertical axis is inverted on nintendo compared to SDL - if (joystick->IsYAxis(event.jaxis.axis)) { - SetAxis(identifier, event.jaxis.axis, -event.jaxis.value / 32767.0f); - break; - } SetAxis(identifier, event.jaxis.axis, event.jaxis.value / 32767.0f); } break; -- cgit v1.2.3 From 746c85b56011b87afb57e37b75953435389fc810 Mon Sep 17 00:00:00 2001 From: german77 Date: Sun, 21 Nov 2021 14:12:01 -0600 Subject: input_common: Move button names to the frontend --- src/input_common/drivers/gc_adapter.cpp | 36 ++++++++++++++++----------------- src/input_common/drivers/gc_adapter.h | 4 ++-- src/input_common/drivers/mouse.cpp | 9 ++++++--- src/input_common/drivers/mouse.h | 2 +- src/input_common/drivers/sdl_driver.cpp | 15 +++++++------- src/input_common/drivers/sdl_driver.h | 2 +- 6 files changed, 35 insertions(+), 33 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/gc_adapter.cpp b/src/input_common/drivers/gc_adapter.cpp index a1b9b6d98..8b6574223 100644 --- a/src/input_common/drivers/gc_adapter.cpp +++ b/src/input_common/drivers/gc_adapter.cpp @@ -481,47 +481,47 @@ AnalogMapping GCAdapter::GetAnalogMappingForDevice(const Common::ParamPackage& p return mapping; } -std::string GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { +Common::Input::ButtonNames GCAdapter::GetUIButtonName(const Common::ParamPackage& params) const { PadButton button = static_cast(params.Get("button", 0)); switch (button) { case PadButton::ButtonLeft: - return "left"; + return Common::Input::ButtonNames::ButtonLeft; case PadButton::ButtonRight: - return "right"; + return Common::Input::ButtonNames::ButtonRight; case PadButton::ButtonDown: - return "down"; + return Common::Input::ButtonNames::ButtonDown; case PadButton::ButtonUp: - return "up"; + return Common::Input::ButtonNames::ButtonUp; case PadButton::TriggerZ: - return "Z"; + return Common::Input::ButtonNames::TriggerZ; case PadButton::TriggerR: - return "R"; + return Common::Input::ButtonNames::TriggerR; case PadButton::TriggerL: - return "L"; + return Common::Input::ButtonNames::TriggerL; case PadButton::ButtonA: - return "A"; + return Common::Input::ButtonNames::ButtonA; case PadButton::ButtonB: - return "B"; + return Common::Input::ButtonNames::ButtonB; case PadButton::ButtonX: - return "X"; + return Common::Input::ButtonNames::ButtonX; case PadButton::ButtonY: - return "Y"; + return Common::Input::ButtonNames::ButtonY; case PadButton::ButtonStart: - return "start"; + return Common::Input::ButtonNames::ButtonStart; default: - return "Unknown GC"; + return Common::Input::ButtonNames::Undefined; } } -std::string GCAdapter::GetUIName(const Common::ParamPackage& params) const { +Common::Input::ButtonNames GCAdapter::GetUIName(const Common::ParamPackage& params) const { if (params.Has("button")) { - return fmt::format("Button {}", GetUIButtonName(params)); + return GetUIButtonName(params); } if (params.Has("axis")) { - return fmt::format("Axis {}", params.Get("axis", 0)); + return Common::Input::ButtonNames::Value; } - return "Bad GC Adapter"; + return Common::Input::ButtonNames::Invalid; } } // namespace InputCommon diff --git a/src/input_common/drivers/gc_adapter.h b/src/input_common/drivers/gc_adapter.h index 3e4747040..8dc51d2e5 100644 --- a/src/input_common/drivers/gc_adapter.h +++ b/src/input_common/drivers/gc_adapter.h @@ -34,7 +34,7 @@ public: std::vector GetInputDevices() const override; ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; - std::string GetUIName(const Common::ParamPackage& params) const override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: enum class PadButton { @@ -112,7 +112,7 @@ private: /// Updates vibration state of all controllers void SendVibrations(); - std::string GetUIButtonName(const Common::ParamPackage& params) const; + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; std::unique_ptr usb_adapter_handle; std::array pads; diff --git a/src/input_common/drivers/mouse.cpp b/src/input_common/drivers/mouse.cpp index 9a9a1987d..752118e97 100644 --- a/src/input_common/drivers/mouse.cpp +++ b/src/input_common/drivers/mouse.cpp @@ -171,12 +171,15 @@ AnalogMapping Mouse::GetAnalogMappingForDevice( return mapping; } -std::string Mouse::GetUIName(const Common::ParamPackage& params) const { +Common::Input::ButtonNames Mouse::GetUIName(const Common::ParamPackage& params) const { if (params.Has("button")) { - return fmt::format("Mouse {}", params.Get("button", 0)); + return Common::Input::ButtonNames::Value; + } + if (params.Has("axis")) { + return Common::Input::ButtonNames::Value; } - return "Bad Mouse"; + return Common::Input::ButtonNames::Invalid; } } // namespace InputCommon diff --git a/src/input_common/drivers/mouse.h b/src/input_common/drivers/mouse.h index 11dd76e14..4a1fd2fd9 100644 --- a/src/input_common/drivers/mouse.h +++ b/src/input_common/drivers/mouse.h @@ -63,7 +63,7 @@ public: std::vector GetInputDevices() const override; AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; - std::string GetUIName(const Common::ParamPackage& params) const override; + Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override; private: void UpdateThread(std::stop_token stop_token); diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index d5af6c09b..90128b6cf 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -869,26 +869,25 @@ MotionMapping SDLDriver::GetMotionMappingForDevice(const Common::ParamPackage& p return mapping; } -std::string SDLDriver::GetUIName(const Common::ParamPackage& params) const { +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 fmt::format("Button {}", params.Get("button", 0)); + return Common::Input::ButtonNames::Value; } if (params.Has("hat")) { - return fmt::format("Hat {}", params.Get("direction", "")); + return Common::Input::ButtonNames::Value; } if (params.Has("axis")) { - return fmt::format("Axis {}", params.Get("axis", "")); + return Common::Input::ButtonNames::Value; } if (params.Has("axis_x") && params.Has("axis_y") && params.Has("axis_z")) { - return fmt::format("Axis {},{},{}", params.Get("axis_x", ""), params.Get("axis_y", ""), - params.Get("axis_z", "")); + return Common::Input::ButtonNames::Value; } if (params.Has("motion")) { - return "SDL motion"; + return Common::Input::ButtonNames::Engine; } - return "Bad SDL"; + return Common::Input::ButtonNames::Invalid; } std::string SDLDriver::GetHatButtonName(u8 direction_value) const { diff --git a/src/input_common/drivers/sdl_driver.h b/src/input_common/drivers/sdl_driver.h index 3faaca984..d03ff4b84 100644 --- a/src/input_common/drivers/sdl_driver.h +++ b/src/input_common/drivers/sdl_driver.h @@ -53,7 +53,7 @@ public: ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& params) override; - std::string GetUIName(const Common::ParamPackage& params) const 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; -- cgit v1.2.3 From 639402850ac65c694967ef6519becb65abe89b39 Mon Sep 17 00:00:00 2001 From: Narr the Reg Date: Fri, 26 Nov 2021 15:45:37 -0600 Subject: input_common: Fully implement UDP controllers --- src/input_common/drivers/udp_client.cpp | 206 ++++++++++++++++++++++++++++++-- src/input_common/drivers/udp_client.h | 56 +++++++++ 2 files changed, 254 insertions(+), 8 deletions(-) (limited to 'src/input_common/drivers') diff --git a/src/input_common/drivers/udp_client.cpp b/src/input_common/drivers/udp_client.cpp index 7cab707da..fdee0f2d5 100644 --- a/src/input_common/drivers/udp_client.cpp +++ b/src/input_common/drivers/udp_client.cpp @@ -103,7 +103,7 @@ private: // Send a request for getting pad data for the pad const Request::PadData pad_data{ - Request::PadData::Flags::AllPorts, + Request::RegisterFlags::AllPads, 0, EMPTY_MAC_ADDRESS, }; @@ -247,7 +247,12 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { for (std::size_t id = 0; id < data.touch.size(); ++id) { const auto touch_pad = data.touch[id]; - const int touch_id = static_cast(client * 2 + id); + const auto touch_axis_x_id = + static_cast(id == 0 ? PadAxes::Touch1X : PadAxes::Touch2X); + const auto touch_axis_y_id = + static_cast(id == 0 ? PadAxes::Touch1Y : PadAxes::Touch2Y); + const auto touch_button_id = + static_cast(id == 0 ? PadButton::Touch1 : PadButton::touch2); // TODO: Use custom calibration per device const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue()); @@ -264,14 +269,35 @@ void UDPClient::OnPadData(Response::PadData data, std::size_t client) { static_cast(max_y - min_y); if (touch_pad.is_active) { - SetAxis(identifier, touch_id * 2, x); - SetAxis(identifier, touch_id * 2 + 1, y); - SetButton(identifier, touch_id, true); + SetAxis(identifier, touch_axis_x_id, x); + SetAxis(identifier, touch_axis_y_id, y); + SetButton(identifier, touch_button_id, true); continue; } - SetAxis(identifier, touch_id * 2, 0); - SetAxis(identifier, touch_id * 2 + 1, 0); - SetButton(identifier, touch_id, false); + SetAxis(identifier, touch_axis_x_id, 0); + SetAxis(identifier, touch_axis_y_id, 0); + SetButton(identifier, touch_button_id, false); + } + + SetAxis(identifier, static_cast(PadAxes::LeftStickX), + (data.left_stick_x - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::LeftStickY), + (data.left_stick_y - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::RightStickX), + (data.right_stick_x - 127.0f) / 127.0f); + SetAxis(identifier, static_cast(PadAxes::RightStickY), + (data.right_stick_y - 127.0f) / 127.0f); + + static constexpr std::array 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(buttons[i]); + SetButton(identifier, button, button_status); } } @@ -317,6 +343,170 @@ void UDPClient::Reset() { } } +std::vector UDPClient::GetInputDevices() const { + std::vector 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(pad_identifier.port)); + identifier.Set("pad", static_cast(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, 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(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(PadAxes::LeftStickX)); + left_analog_params.Set("axis_y", static_cast(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(PadAxes::RightStickX)); + right_analog_params.Set("axis_y", static_cast(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(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& success_callback, const std::function& failure_callback) { diff --git a/src/input_common/drivers/udp_client.h b/src/input_common/drivers/udp_client.h index 1f02adba5..5d483f26b 100644 --- a/src/input_common/drivers/udp_client.h +++ b/src/input_common/drivers/udp_client.h @@ -56,7 +56,61 @@ public: void ReloadSockets(); + /// Used for automapping features + std::vector 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; + 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, + }; + + enum class PadAxes : u8 { + LeftStickX, + LeftStickY, + RightStickX, + RightStickY, + AnalogLeft, + AnalogDown, + AnalogRight, + AnalogUp, + AnalogSquare, + AnalogCross, + AnalogCircle, + AnalogTriangle, + AnalogR1, + AnalogL1, + AnalogR2, + AnalogL3, + AnalogR3, + Touch1X, + Touch1Y, + Touch2X, + Touch2Y, + Undefined, + }; + struct PadData { std::size_t pad_index{}; bool connected{}; @@ -90,6 +144,8 @@ private: const PadIdentifier GetPadIdentifier(std::size_t pad_index) const; const Common::UUID GetHostUUID(const std::string host) const; + Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const; + // Allocate clients for 8 udp servers static constexpr std::size_t MAX_UDP_CLIENTS = 8; static constexpr std::size_t PADS_PER_CLIENT = 4; -- cgit v1.2.3