diff options
Diffstat (limited to 'src/input_common')
-rw-r--r-- | src/input_common/CMakeLists.txt | 13 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_adapter.cpp | 426 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_adapter.h | 135 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_poller.cpp | 288 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_poller.h | 67 | ||||
-rw-r--r-- | src/input_common/keyboard.cpp | 2 | ||||
-rw-r--r-- | src/input_common/main.cpp | 248 | ||||
-rw-r--r-- | src/input_common/main.h | 147 | ||||
-rw-r--r-- | src/input_common/motion_emu.cpp | 19 | ||||
-rw-r--r-- | src/input_common/motion_input.cpp | 181 | ||||
-rw-r--r-- | src/input_common/motion_input.h | 68 | ||||
-rw-r--r-- | src/input_common/sdl/sdl.h | 19 | ||||
-rw-r--r-- | src/input_common/sdl/sdl_impl.cpp | 438 | ||||
-rw-r--r-- | src/input_common/sdl/sdl_impl.h | 8 | ||||
-rw-r--r-- | src/input_common/settings.cpp | 40 | ||||
-rw-r--r-- | src/input_common/settings.h | 352 | ||||
-rw-r--r-- | src/input_common/touch_from_button.cpp | 50 | ||||
-rw-r--r-- | src/input_common/touch_from_button.h | 23 | ||||
-rw-r--r-- | src/input_common/udp/client.cpp | 185 | ||||
-rw-r--r-- | src/input_common/udp/client.h | 77 | ||||
-rw-r--r-- | src/input_common/udp/udp.cpp | 177 | ||||
-rw-r--r-- | src/input_common/udp/udp.h | 54 |
22 files changed, 2712 insertions, 305 deletions
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index a9c2392b1..09361e37e 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt @@ -7,6 +7,16 @@ add_library(input_common STATIC main.h motion_emu.cpp motion_emu.h + motion_input.cpp + motion_input.h + settings.cpp + settings.h + touch_from_button.cpp + touch_from_button.h + gcadapter/gc_adapter.cpp + gcadapter/gc_adapter.h + gcadapter/gc_poller.cpp + gcadapter/gc_poller.h sdl/sdl.cpp sdl/sdl.h udp/client.cpp @@ -26,5 +36,8 @@ if(SDL2_FOUND) target_compile_definitions(input_common PRIVATE HAVE_SDL2) endif() +target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR}) +target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES}) + create_target_directory_groups(input_common) target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp new file mode 100644 index 000000000..89c148aba --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -0,0 +1,426 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#include <chrono> +#include <thread> + +#ifdef _MSC_VER +#pragma warning(push) +#pragma warning(disable : 4200) // nonstandard extension used : zero-sized array in struct/union +#endif +#include <libusb.h> +#ifdef _MSC_VER +#pragma warning(pop) +#endif + +#include "common/logging/log.h" +#include "common/param_package.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/settings.h" + +namespace GCAdapter { + +/// Used to loop through and assign button in poller +constexpr std::array<PadButton, 12> PadButtonArray{ + PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, PadButton::PAD_BUTTON_DOWN, + PadButton::PAD_BUTTON_UP, PadButton::PAD_TRIGGER_Z, PadButton::PAD_TRIGGER_R, + PadButton::PAD_TRIGGER_L, PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, + PadButton::PAD_BUTTON_X, PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_START, +}; + +Adapter::Adapter() { + if (usb_adapter_handle != nullptr) { + return; + } + LOG_INFO(Input, "GC Adapter Initialization started"); + + const int init_res = libusb_init(&libusb_ctx); + if (init_res == LIBUSB_SUCCESS) { + Setup(); + } else { + LOG_ERROR(Input, "libusb could not be initialized. failed with error = {}", init_res); + } +} + +GCPadStatus Adapter::GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload) { + GCPadStatus pad = {}; + const std::size_t offset = 1 + (9 * port); + + adapter_controllers_status[port] = static_cast<ControllerTypes>(adapter_payload[offset] >> 4); + + static constexpr std::array<PadButton, 8> b1_buttons{ + PadButton::PAD_BUTTON_A, PadButton::PAD_BUTTON_B, PadButton::PAD_BUTTON_X, + PadButton::PAD_BUTTON_Y, PadButton::PAD_BUTTON_LEFT, PadButton::PAD_BUTTON_RIGHT, + PadButton::PAD_BUTTON_DOWN, PadButton::PAD_BUTTON_UP, + }; + + static constexpr std::array<PadButton, 4> b2_buttons{ + PadButton::PAD_BUTTON_START, + PadButton::PAD_TRIGGER_Z, + PadButton::PAD_TRIGGER_R, + PadButton::PAD_TRIGGER_L, + }; + + static constexpr std::array<PadAxes, 6> axes{ + PadAxes::StickX, PadAxes::StickY, PadAxes::SubstickX, + PadAxes::SubstickY, PadAxes::TriggerLeft, PadAxes::TriggerRight, + }; + + if (adapter_controllers_status[port] == ControllerTypes::None && !get_origin[port]) { + // Controller may have been disconnected, recalibrate if reconnected. + get_origin[port] = true; + } + + if (adapter_controllers_status[port] != ControllerTypes::None) { + const u8 b1 = adapter_payload[offset + 1]; + const u8 b2 = adapter_payload[offset + 2]; + + for (std::size_t i = 0; i < b1_buttons.size(); ++i) { + if ((b1 & (1U << i)) != 0) { + pad.button |= static_cast<u16>(b1_buttons[i]); + } + } + + for (std::size_t j = 0; j < b2_buttons.size(); ++j) { + if ((b2 & (1U << j)) != 0) { + pad.button |= static_cast<u16>(b2_buttons[j]); + } + } + for (PadAxes axis : axes) { + const std::size_t index = static_cast<std::size_t>(axis); + pad.axis_values[index] = adapter_payload[offset + 3 + index]; + } + + if (get_origin[port]) { + origin_status[port].axis_values = pad.axis_values; + get_origin[port] = false; + } + } + return pad; +} + +void Adapter::PadToState(const GCPadStatus& pad, GCState& state) { + for (const auto& button : PadButtonArray) { + const u16 button_value = static_cast<u16>(button); + state.buttons.insert_or_assign(button_value, pad.button & button_value); + } + + for (size_t i = 0; i < pad.axis_values.size(); ++i) { + state.axes.insert_or_assign(static_cast<u8>(i), pad.axis_values[i]); + } +} + +void Adapter::Read() { + LOG_DEBUG(Input, "GC Adapter Read() thread started"); + + int payload_size; + std::array<u8, 37> adapter_payload; + std::array<GCPadStatus, 4> pads; + + while (adapter_thread_running) { + libusb_interrupt_transfer(usb_adapter_handle, input_endpoint, adapter_payload.data(), + sizeof(adapter_payload), &payload_size, 16); + + if (payload_size != sizeof(adapter_payload) || adapter_payload[0] != LIBUSB_DT_HID) { + LOG_ERROR(Input, + "Error reading payload (size: {}, type: {:02x}) Is the adapter connected?", + payload_size, adapter_payload[0]); + adapter_thread_running = false; // error reading from adapter, stop reading. + break; + } + for (std::size_t port = 0; port < pads.size(); ++port) { + pads[port] = GetPadStatus(port, adapter_payload); + if (DeviceConnected(port) && configuring) { + if (pads[port].button != 0) { + pad_queue[port].Push(pads[port]); + } + + // Accounting for a threshold here to ensure an intentional press + for (size_t i = 0; i < pads[port].axis_values.size(); ++i) { + const u8 value = pads[port].axis_values[i]; + const u8 origin = origin_status[port].axis_values[i]; + + if (value > origin + pads[port].THRESHOLD || + value < origin - pads[port].THRESHOLD) { + pads[port].axis = static_cast<PadAxes>(i); + pads[port].axis_value = pads[port].axis_values[i]; + pad_queue[port].Push(pads[port]); + } + } + } + PadToState(pads[port], state[port]); + } + std::this_thread::yield(); + } +} + +void Adapter::Setup() { + // Initialize all controllers as unplugged + adapter_controllers_status.fill(ControllerTypes::None); + // Initialize all ports to store axis origin values + get_origin.fill(true); + + // pointer to list of connected usb devices + libusb_device** devices{}; + + // populate the list of devices, get the count + const ssize_t device_count = libusb_get_device_list(libusb_ctx, &devices); + if (device_count < 0) { + LOG_ERROR(Input, "libusb_get_device_list failed with error: {}", device_count); + return; + } + + if (devices != nullptr) { + for (std::size_t index = 0; index < static_cast<std::size_t>(device_count); ++index) { + if (CheckDeviceAccess(devices[index])) { + // GC Adapter found and accessible, registering it + GetGCEndpoint(devices[index]); + break; + } + } + libusb_free_device_list(devices, 1); + } +} + +bool Adapter::CheckDeviceAccess(libusb_device* device) { + libusb_device_descriptor desc; + const int get_descriptor_error = libusb_get_device_descriptor(device, &desc); + if (get_descriptor_error) { + // could not acquire the descriptor, no point in trying to use it. + LOG_ERROR(Input, "libusb_get_device_descriptor failed with error: {}", + get_descriptor_error); + return false; + } + + if (desc.idVendor != 0x057e || desc.idProduct != 0x0337) { + // This isn't the device we are looking for. + return false; + } + const int open_error = libusb_open(device, &usb_adapter_handle); + + if (open_error == LIBUSB_ERROR_ACCESS) { + LOG_ERROR(Input, "Yuzu can not gain access to this device: ID {:04X}:{:04X}.", + desc.idVendor, desc.idProduct); + return false; + } + if (open_error) { + LOG_ERROR(Input, "libusb_open failed to open device with error = {}", open_error); + return false; + } + + int kernel_driver_error = libusb_kernel_driver_active(usb_adapter_handle, 0); + if (kernel_driver_error == 1) { + kernel_driver_error = libusb_detach_kernel_driver(usb_adapter_handle, 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) { + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + const int interface_claim_error = libusb_claim_interface(usb_adapter_handle, 0); + if (interface_claim_error) { + LOG_ERROR(Input, "libusb_claim_interface failed with error = {}", interface_claim_error); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + return false; + } + + return true; +} + +void Adapter::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; + } + + 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) { + 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, output_endpoint, &clear_payload, + sizeof(clear_payload), nullptr, 16); + + adapter_thread_running = true; + adapter_input_thread = std::thread(&Adapter::Read, this); +} + +Adapter::~Adapter() { + Reset(); +} + +void Adapter::Reset() { + if (adapter_thread_running) { + adapter_thread_running = false; + } + if (adapter_input_thread.joinable()) { + adapter_input_thread.join(); + } + + adapter_controllers_status.fill(ControllerTypes::None); + get_origin.fill(true); + + if (usb_adapter_handle) { + libusb_release_interface(usb_adapter_handle, 1); + libusb_close(usb_adapter_handle); + usb_adapter_handle = nullptr; + } + + if (libusb_ctx) { + libusb_exit(libusb_ctx); + } +} + +std::vector<Common::ParamPackage> Adapter::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + for (std::size_t port = 0; port < state.size(); ++port) { + if (!DeviceConnected(port)) { + continue; + } + std::string name = fmt::format("Gamecube Controller {}", port); + devices.emplace_back(Common::ParamPackage{ + {"class", "gcpad"}, + {"display", std::move(name)}, + {"port", std::to_string(port)}, + }); + } + return devices; +} + +InputCommon::ButtonMapping Adapter::GetButtonMappingForDevice( + const Common::ParamPackage& params) const { + // This list is missing ZL/ZR since those are not considered buttons. + // We will add those afterwards + // This list also excludes any button that can't be really mapped + static constexpr std::array<std::pair<Settings::NativeButton::Values, PadButton>, 12> + switch_to_gcadapter_button = { + std::pair{Settings::NativeButton::A, PadButton::PAD_BUTTON_A}, + {Settings::NativeButton::B, PadButton::PAD_BUTTON_B}, + {Settings::NativeButton::X, PadButton::PAD_BUTTON_X}, + {Settings::NativeButton::Y, PadButton::PAD_BUTTON_Y}, + {Settings::NativeButton::Plus, PadButton::PAD_BUTTON_START}, + {Settings::NativeButton::DLeft, PadButton::PAD_BUTTON_LEFT}, + {Settings::NativeButton::DUp, PadButton::PAD_BUTTON_UP}, + {Settings::NativeButton::DRight, PadButton::PAD_BUTTON_RIGHT}, + {Settings::NativeButton::DDown, PadButton::PAD_BUTTON_DOWN}, + {Settings::NativeButton::SL, PadButton::PAD_TRIGGER_L}, + {Settings::NativeButton::SR, PadButton::PAD_TRIGGER_R}, + {Settings::NativeButton::R, PadButton::PAD_TRIGGER_Z}, + }; + if (!params.Has("port")) { + return {}; + } + + InputCommon::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<int>(gcadapter_button)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + + // Add the missing bindings for ZL/ZR + static constexpr std::array<std::pair<Settings::NativeButton::Values, PadAxes>, 2> + switch_to_gcadapter_axis = { + std::pair{Settings::NativeButton::ZL, PadAxes::TriggerLeft}, + {Settings::NativeButton::ZR, PadAxes::TriggerRight}, + }; + for (const auto& [switch_button, 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<int>(PadButton::PAD_STICK)); + button_params.Set("axis", static_cast<int>(gcadapter_axis)); + mapping.insert_or_assign(switch_button, std::move(button_params)); + } + return mapping; +} + +InputCommon::AnalogMapping Adapter::GetAnalogMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("port")) { + return {}; + } + + InputCommon::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<int>(PadAxes::StickX)); + left_analog_params.Set("axis_y", static_cast<int>(PadAxes::StickY)); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, std::move(left_analog_params)); + Common::ParamPackage right_analog_params; + right_analog_params.Set("engine", "gcpad"); + right_analog_params.Set("port", params.Get("port", 0)); + right_analog_params.Set("axis_x", static_cast<int>(PadAxes::SubstickX)); + right_analog_params.Set("axis_y", static_cast<int>(PadAxes::SubstickY)); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, std::move(right_analog_params)); + return mapping; +} + +bool Adapter::DeviceConnected(std::size_t port) const { + return adapter_controllers_status[port] != ControllerTypes::None; +} + +void Adapter::ResetDeviceType(std::size_t port) { + adapter_controllers_status[port] = ControllerTypes::None; +} + +void Adapter::BeginConfiguration() { + get_origin.fill(true); + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = true; +} + +void Adapter::EndConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = false; +} + +std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() { + return pad_queue; +} + +const std::array<Common::SPSCQueue<GCPadStatus>, 4>& Adapter::GetPadQueue() const { + return pad_queue; +} + +std::array<GCState, 4>& Adapter::GetPadState() { + return state; +} + +const std::array<GCState, 4>& Adapter::GetPadState() const { + return state; +} + +int Adapter::GetOriginValue(int port, int axis) const { + return origin_status[port].axis_values[axis]; +} + +} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h new file mode 100644 index 000000000..75bf9fe74 --- /dev/null +++ b/src/input_common/gcadapter/gc_adapter.h @@ -0,0 +1,135 @@ +// Copyright 2014 Dolphin Emulator Project +// Licensed under GPLv2+ +// Refer to the license.txt file included. + +#pragma once +#include <algorithm> +#include <functional> +#include <mutex> +#include <thread> +#include <unordered_map> +#include "common/common_types.h" +#include "common/threadsafe_queue.h" +#include "input_common/main.h" + +struct libusb_context; +struct libusb_device; +struct libusb_device_handle; + +namespace GCAdapter { + +enum class PadButton { + PAD_BUTTON_LEFT = 0x0001, + PAD_BUTTON_RIGHT = 0x0002, + PAD_BUTTON_DOWN = 0x0004, + PAD_BUTTON_UP = 0x0008, + PAD_TRIGGER_Z = 0x0010, + PAD_TRIGGER_R = 0x0020, + PAD_TRIGGER_L = 0x0040, + PAD_BUTTON_A = 0x0100, + PAD_BUTTON_B = 0x0200, + PAD_BUTTON_X = 0x0400, + PAD_BUTTON_Y = 0x0800, + PAD_BUTTON_START = 0x1000, + // Below is for compatibility with "AxisButton" type + PAD_STICK = 0x2000, +}; + +extern const std::array<PadButton, 12> PadButtonArray; + +enum class PadAxes : u8 { + StickX, + StickY, + SubstickX, + SubstickY, + TriggerLeft, + TriggerRight, + Undefined, +}; + +struct GCPadStatus { + u16 button{}; // Or-ed PAD_BUTTON_* and PAD_TRIGGER_* bits + + std::array<u8, 6> axis_values{}; // Triggers and sticks, following indices defined in PadAxes + static constexpr u8 THRESHOLD = 50; // Threshold for axis press for polling + + u8 port{}; + PadAxes axis{PadAxes::Undefined}; + u8 axis_value{255}; +}; + +struct GCState { + std::unordered_map<int, bool> buttons; + std::unordered_map<int, u16> axes; +}; + +enum class ControllerTypes { None, Wired, Wireless }; + +class Adapter { +public: + /// Initialize the GC Adapter capture and read sequence + Adapter(); + + /// Close the adapter read thread and release the adapter + ~Adapter(); + /// Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + std::vector<Common::ParamPackage> GetInputDevices() const; + InputCommon::ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) const; + InputCommon::AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) const; + + /// Returns true if there is a device connected to port + bool DeviceConnected(std::size_t port) const; + + std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue(); + const std::array<Common::SPSCQueue<GCPadStatus>, 4>& GetPadQueue() const; + + std::array<GCState, 4>& GetPadState(); + const std::array<GCState, 4>& GetPadState() const; + + int GetOriginValue(int port, int axis) const; + +private: + GCPadStatus GetPadStatus(std::size_t port, const std::array<u8, 37>& adapter_payload); + + void PadToState(const GCPadStatus& pad, GCState& state); + + void Read(); + + /// Resets status of device connected to port + void ResetDeviceType(std::size_t port); + + /// Returns true if we successfully gain access to GC Adapter + bool CheckDeviceAccess(libusb_device* device); + + /// Captures GC Adapter endpoint address, + void GetGCEndpoint(libusb_device* device); + + /// For shutting down, clear all data, join all threads, release usb + void Reset(); + + /// For use in initialization, querying devices to find the adapter + void Setup(); + + libusb_device_handle* usb_adapter_handle = nullptr; + + std::thread adapter_input_thread; + bool adapter_thread_running; + + libusb_context* libusb_ctx; + + u8 input_endpoint = 0; + u8 output_endpoint = 0; + + bool configuring = false; + + std::array<GCState, 4> state; + std::array<bool, 4> get_origin; + std::array<GCPadStatus, 4> origin_status; + std::array<Common::SPSCQueue<GCPadStatus>, 4> pad_queue; + std::array<ControllerTypes, 4> adapter_controllers_status{}; +}; + +} // namespace GCAdapter diff --git a/src/input_common/gcadapter/gc_poller.cpp b/src/input_common/gcadapter/gc_poller.cpp new file mode 100644 index 000000000..92e9e8e89 --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.cpp @@ -0,0 +1,288 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include <atomic> +#include <list> +#include <mutex> +#include <utility> +#include "common/assert.h" +#include "common/threadsafe_queue.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/gcadapter/gc_poller.h" + +namespace InputCommon { + +class GCButton final : public Input::ButtonDevice { +public: + explicit GCButton(int port_, int button_, const GCAdapter::Adapter* adapter) + : port(port_), button(button_), gcadapter(adapter) {} + + ~GCButton() override; + + bool GetStatus() const override { + if (gcadapter->DeviceConnected(port)) { + return gcadapter->GetPadState()[port].buttons.at(button); + } + return false; + } + +private: + const int port; + const int button; + const GCAdapter::Adapter* gcadapter; +}; + +class GCAxisButton final : public Input::ButtonDevice { +public: + explicit GCAxisButton(int port_, int axis_, float threshold_, bool trigger_if_greater_, + const GCAdapter::Adapter* adapter) + : port(port_), axis(axis_), threshold(threshold_), trigger_if_greater(trigger_if_greater_), + gcadapter(adapter), + origin_value(static_cast<float>(adapter->GetOriginValue(port_, axis_))) {} + + bool GetStatus() const override { + if (gcadapter->DeviceConnected(port)) { + const float current_axis_value = gcadapter->GetPadState()[port].axes.at(axis); + const float axis_value = (current_axis_value - origin_value) / 128.0f; + if (trigger_if_greater) { + // TODO: Might be worthwile to set a slider for the trigger threshold. It is + // currently always set to 0.5 in configure_input_player.cpp ZL/ZR HandleClick + return axis_value > threshold; + } + return axis_value < -threshold; + } + return false; + } + +private: + const int port; + const int axis; + float threshold; + bool trigger_if_greater; + const GCAdapter::Adapter* gcadapter; + const float origin_value; +}; + +GCButtonFactory::GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) + : adapter(std::move(adapter_)) {} + +GCButton::~GCButton() = default; + +std::unique_ptr<Input::ButtonDevice> GCButtonFactory::Create(const Common::ParamPackage& params) { + const int button_id = params.Get("button", 0); + const int port = params.Get("port", 0); + + constexpr int PAD_STICK_ID = static_cast<u16>(GCAdapter::PadButton::PAD_STICK); + + // button is not an axis/stick button + if (button_id != PAD_STICK_ID) { + return std::make_unique<GCButton>(port, button_id, adapter.get()); + } + + // For Axis buttons, used by the binary sticks. + if (button_id == PAD_STICK_ID) { + const int axis = params.Get("axis", 0); + const float threshold = params.Get("threshold", 0.25f); + const std::string direction_name = params.Get("direction", ""); + bool trigger_if_greater; + if (direction_name == "+") { + trigger_if_greater = true; + } else if (direction_name == "-") { + trigger_if_greater = false; + } else { + trigger_if_greater = true; + LOG_ERROR(Input, "Unknown direction {}", direction_name); + } + return std::make_unique<GCAxisButton>(port, axis, threshold, trigger_if_greater, + adapter.get()); + } + + UNREACHABLE(); + return nullptr; +} + +Common::ParamPackage GCButtonFactory::GetNextInput() const { + Common::ParamPackage params; + GCAdapter::GCPadStatus pad; + auto& queue = adapter->GetPadQueue(); + for (std::size_t port = 0; port < queue.size(); ++port) { + while (queue[port].Pop(pad)) { + // This while loop will break on the earliest detected button + params.Set("engine", "gcpad"); + params.Set("port", static_cast<int>(port)); + for (const auto& button : GCAdapter::PadButtonArray) { + const u16 button_value = static_cast<u16>(button); + if (pad.button & button_value) { + params.Set("button", button_value); + break; + } + } + + // For Axis button implementation + if (pad.axis != GCAdapter::PadAxes::Undefined) { + params.Set("axis", static_cast<u8>(pad.axis)); + params.Set("button", static_cast<u16>(GCAdapter::PadButton::PAD_STICK)); + if (pad.axis_value > 128) { + params.Set("direction", "+"); + params.Set("threshold", "0.25"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.25"); + } + break; + } + } + } + return params; +} + +void GCButtonFactory::BeginConfiguration() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCButtonFactory::EndConfiguration() { + polling = false; + adapter->EndConfiguration(); +} + +class GCAnalog final : public Input::AnalogDevice { +public: + GCAnalog(int port_, int axis_x_, int axis_y_, float deadzone_, + const GCAdapter::Adapter* adapter, float range_) + : port(port_), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), gcadapter(adapter), + origin_value_x(static_cast<float>(adapter->GetOriginValue(port_, axis_x_))), + origin_value_y(static_cast<float>(adapter->GetOriginValue(port_, axis_y_))), + range(range_) {} + + float GetAxis(int axis) const { + if (gcadapter->DeviceConnected(port)) { + std::lock_guard lock{mutex}; + const auto origin_value = axis % 2 == 0 ? origin_value_x : origin_value_y; + return (gcadapter->GetPadState()[port].axes.at(axis) - origin_value) / (100.0f * range); + } + return 0.0f; + } + + std::pair<float, float> GetAnalog(int axis_x, int axis_y) const { + float x = GetAxis(axis_x); + float y = GetAxis(axis_y); + + // Make sure the coordinates are in the unit circle, + // otherwise normalize it. + float r = x * x + y * y; + if (r > 1.0f) { + r = std::sqrt(r); + x /= r; + y /= r; + } + + return {x, y}; + } + + std::tuple<float, float> GetStatus() const override { + const auto [x, y] = GetAnalog(axis_x, axis_y); + const float r = std::sqrt((x * x) + (y * y)); + if (r > deadzone) { + return {x / r * (r - deadzone) / (1 - deadzone), + y / r * (r - deadzone) / (1 - deadzone)}; + } + return {0.0f, 0.0f}; + } + + bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { + const auto [x, y] = GetStatus(); + const float directional_deadzone = 0.5f; + switch (direction) { + case Input::AnalogDirection::RIGHT: + return x > directional_deadzone; + case Input::AnalogDirection::LEFT: + return x < -directional_deadzone; + case Input::AnalogDirection::UP: + return y > directional_deadzone; + case Input::AnalogDirection::DOWN: + return y < -directional_deadzone; + } + return false; + } + +private: + const int port; + const int axis_x; + const int axis_y; + const float deadzone; + const GCAdapter::Adapter* gcadapter; + const float origin_value_x; + const float origin_value_y; + const float range; + mutable std::mutex mutex; +}; + +/// An analog device factory that creates analog devices from GC Adapter +GCAnalogFactory::GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_) + : adapter(std::move(adapter_)) {} + +/** + * Creates analog device from joystick axes + * @param params contains parameters for creating the device: + * - "port": the nth gcpad on the adapter + * - "axis_x": the index of the axis to be bind as x-axis + * - "axis_y": the index of the axis to be bind as y-axis + */ +std::unique_ptr<Input::AnalogDevice> GCAnalogFactory::Create(const Common::ParamPackage& params) { + const int port = params.Get("port", 0); + const int axis_x = params.Get("axis_x", 0); + const int axis_y = params.Get("axis_y", 1); + const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); + + return std::make_unique<GCAnalog>(port, axis_x, axis_y, deadzone, adapter.get(), range); +} + +void GCAnalogFactory::BeginConfiguration() { + polling = true; + adapter->BeginConfiguration(); +} + +void GCAnalogFactory::EndConfiguration() { + polling = false; + adapter->EndConfiguration(); +} + +Common::ParamPackage GCAnalogFactory::GetNextInput() { + GCAdapter::GCPadStatus pad; + auto& queue = adapter->GetPadQueue(); + for (std::size_t port = 0; port < queue.size(); ++port) { + while (queue[port].Pop(pad)) { + if (pad.axis == GCAdapter::PadAxes::Undefined || + std::abs((pad.axis_value - 128.0f) / 128.0f) < 0.1) { + continue; + } + // An analog device needs two axes, so we need to store the axis for later and wait for + // a second input event. The axes also must be from the same joystick. + const u8 axis = static_cast<u8>(pad.axis); + if (analog_x_axis == -1) { + analog_x_axis = axis; + controller_number = static_cast<int>(port); + } else if (analog_y_axis == -1 && analog_x_axis != axis && + controller_number == static_cast<int>(port)) { + analog_y_axis = axis; + } + } + } + Common::ParamPackage params; + if (analog_x_axis != -1 && analog_y_axis != -1) { + params.Set("engine", "gcpad"); + params.Set("port", controller_number); + params.Set("axis_x", analog_x_axis); + params.Set("axis_y", analog_y_axis); + analog_x_axis = -1; + analog_y_axis = -1; + controller_number = -1; + return params; + } + return params; +} + +} // namespace InputCommon diff --git a/src/input_common/gcadapter/gc_poller.h b/src/input_common/gcadapter/gc_poller.h new file mode 100644 index 000000000..0527f328f --- /dev/null +++ b/src/input_common/gcadapter/gc_poller.h @@ -0,0 +1,67 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" +#include "input_common/gcadapter/gc_adapter.h" + +namespace InputCommon { + +/** + * A button device factory representing a gcpad. It receives gcpad events and forward them + * to all button devices it created. + */ +class GCButtonFactory final : public Input::Factory<Input::ButtonDevice> { +public: + explicit GCButtonFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); + + /** + * Creates a button device from a button press + * @param params contains parameters for creating the device: + * - "code": the code of the key to bind with the button + */ + std::unique_ptr<Input::ButtonDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput() const; + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<GCAdapter::Adapter> adapter; + bool polling = false; +}; + +/// An analog device factory that creates analog devices from GC Adapter +class GCAnalogFactory final : public Input::Factory<Input::AnalogDevice> { +public: + explicit GCAnalogFactory(std::shared_ptr<GCAdapter::Adapter> adapter_); + + std::unique_ptr<Input::AnalogDevice> Create(const Common::ParamPackage& params) override; + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<GCAdapter::Adapter> adapter; + int analog_x_axis = -1; + int analog_y_axis = -1; + int controller_number = -1; + bool polling = false; +}; + +} // namespace InputCommon diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp index 078374be5..afb8e6612 100644 --- a/src/input_common/keyboard.cpp +++ b/src/input_common/keyboard.cpp @@ -76,7 +76,7 @@ std::unique_ptr<Input::ButtonDevice> Keyboard::Create(const Common::ParamPackage int key_code = params.Get("code", 0); std::unique_ptr<KeyButton> button = std::make_unique<KeyButton>(key_button_list); key_button_list->AddKeyButton(key_code, button.get()); - return std::move(button); + return button; } void Keyboard::PressKey(int key_code) { diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index 95e351e24..8da829132 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -6,9 +6,13 @@ #include <thread> #include "common/param_package.h" #include "input_common/analog_from_button.h" +#include "input_common/gcadapter/gc_adapter.h" +#include "input_common/gcadapter/gc_poller.h" #include "input_common/keyboard.h" #include "input_common/main.h" #include "input_common/motion_emu.h" +#include "input_common/touch_from_button.h" +#include "input_common/udp/client.h" #include "input_common/udp/udp.h" #ifdef HAVE_SDL2 #include "input_common/sdl/sdl.h" @@ -16,46 +20,224 @@ namespace InputCommon { -static std::shared_ptr<Keyboard> keyboard; -static std::shared_ptr<MotionEmu> motion_emu; +struct InputSubsystem::Impl { + void Initialize() { + gcadapter = std::make_shared<GCAdapter::Adapter>(); + gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); + Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); + gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); + Input::RegisterFactory<Input::AnalogDevice>("gcpad", gcanalog); + + keyboard = std::make_shared<Keyboard>(); + Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); + Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", + std::make_shared<AnalogFromButton>()); + motion_emu = std::make_shared<MotionEmu>(); + Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); + Input::RegisterFactory<Input::TouchDevice>("touch_from_button", + std::make_shared<TouchFromButtonFactory>()); + #ifdef HAVE_SDL2 -static std::unique_ptr<SDL::State> sdl; + sdl = SDL::Init(); #endif -static std::unique_ptr<CemuhookUDP::State> udp; -void Init() { - keyboard = std::make_shared<Keyboard>(); - Input::RegisterFactory<Input::ButtonDevice>("keyboard", keyboard); - Input::RegisterFactory<Input::AnalogDevice>("analog_from_button", - std::make_shared<AnalogFromButton>()); - motion_emu = std::make_shared<MotionEmu>(); - Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); + udp = std::make_shared<InputCommon::CemuhookUDP::Client>(); + udpmotion = std::make_shared<UDPMotionFactory>(udp); + Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", udpmotion); + udptouch = std::make_shared<UDPTouchFactory>(udp); + Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", udptouch); + } + void Shutdown() { + Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); + keyboard.reset(); + Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); + Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); + motion_emu.reset(); + Input::UnregisterFactory<Input::TouchDevice>("touch_from_button"); #ifdef HAVE_SDL2 - sdl = SDL::Init(); + sdl.reset(); #endif + Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); + Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); - udp = CemuhookUDP::Init(); -} + gcbuttons.reset(); + gcanalog.reset(); + + Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); + Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); + + udpmotion.reset(); + udptouch.reset(); + } + + [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const { + std::vector<Common::ParamPackage> devices = { + Common::ParamPackage{{"display", "Any"}, {"class", "any"}}, + Common::ParamPackage{{"display", "Keyboard/Mouse"}, {"class", "key"}}, + }; +#ifdef HAVE_SDL2 + auto sdl_devices = sdl->GetInputDevices(); + devices.insert(devices.end(), sdl_devices.begin(), sdl_devices.end()); +#endif + auto udp_devices = udp->GetInputDevices(); + devices.insert(devices.end(), udp_devices.begin(), udp_devices.end()); + auto gcpad_devices = gcadapter->GetInputDevices(); + devices.insert(devices.end(), gcpad_devices.begin(), gcpad_devices.end()); + return devices; + } + + [[nodiscard]] AnalogMapping GetAnalogMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("class") || params.Get("class", "") == "any") { + return {}; + } + if (params.Get("class", "") == "key") { + // TODO consider returning the SDL key codes for the default keybindings + return {}; + } + if (params.Get("class", "") == "gcpad") { + return gcadapter->GetAnalogMappingForDevice(params); + } +#ifdef HAVE_SDL2 + if (params.Get("class", "") == "sdl") { + return sdl->GetAnalogMappingForDevice(params); + } +#endif + return {}; + } + + [[nodiscard]] ButtonMapping GetButtonMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("class") || params.Get("class", "") == "any") { + return {}; + } + if (params.Get("class", "") == "key") { + // TODO consider returning the SDL key codes for the default keybindings + return {}; + } + if (params.Get("class", "") == "gcpad") { + return gcadapter->GetButtonMappingForDevice(params); + } +#ifdef HAVE_SDL2 + if (params.Get("class", "") == "sdl") { + return sdl->GetButtonMappingForDevice(params); + } +#endif + return {}; + } + + [[nodiscard]] MotionMapping GetMotionMappingForDevice( + const Common::ParamPackage& params) const { + if (!params.Has("class") || params.Get("class", "") == "any") { + return {}; + } + if (params.Get("class", "") == "cemuhookudp") { + // TODO return the correct motion device + return {}; + } + return {}; + } -void Shutdown() { - Input::UnregisterFactory<Input::ButtonDevice>("keyboard"); - keyboard.reset(); - Input::UnregisterFactory<Input::AnalogDevice>("analog_from_button"); - Input::UnregisterFactory<Input::MotionDevice>("motion_emu"); - motion_emu.reset(); + std::shared_ptr<Keyboard> keyboard; + std::shared_ptr<MotionEmu> motion_emu; #ifdef HAVE_SDL2 - sdl.reset(); + std::unique_ptr<SDL::State> sdl; #endif - udp.reset(); + std::shared_ptr<GCButtonFactory> gcbuttons; + std::shared_ptr<GCAnalogFactory> gcanalog; + std::shared_ptr<UDPMotionFactory> udpmotion; + std::shared_ptr<UDPTouchFactory> udptouch; + std::shared_ptr<CemuhookUDP::Client> udp; + std::shared_ptr<GCAdapter::Adapter> gcadapter; +}; + +InputSubsystem::InputSubsystem() : impl{std::make_unique<Impl>()} {} + +InputSubsystem::~InputSubsystem() = default; + +void InputSubsystem::Initialize() { + impl->Initialize(); +} + +void InputSubsystem::Shutdown() { + impl->Shutdown(); +} + +Keyboard* InputSubsystem::GetKeyboard() { + return impl->keyboard.get(); +} + +const Keyboard* InputSubsystem::GetKeyboard() const { + return impl->keyboard.get(); +} + +MotionEmu* InputSubsystem::GetMotionEmu() { + return impl->motion_emu.get(); +} + +const MotionEmu* InputSubsystem::GetMotionEmu() const { + return impl->motion_emu.get(); +} + +std::vector<Common::ParamPackage> InputSubsystem::GetInputDevices() const { + return impl->GetInputDevices(); } -Keyboard* GetKeyboard() { - return keyboard.get(); +AnalogMapping InputSubsystem::GetAnalogMappingForDevice(const Common::ParamPackage& device) const { + return impl->GetAnalogMappingForDevice(device); } -MotionEmu* GetMotionEmu() { - return motion_emu.get(); +ButtonMapping InputSubsystem::GetButtonMappingForDevice(const Common::ParamPackage& device) const { + return impl->GetButtonMappingForDevice(device); +} + +GCAnalogFactory* InputSubsystem::GetGCAnalogs() { + return impl->gcanalog.get(); +} + +const GCAnalogFactory* InputSubsystem::GetGCAnalogs() const { + return impl->gcanalog.get(); +} + +GCButtonFactory* InputSubsystem::GetGCButtons() { + return impl->gcbuttons.get(); +} + +const GCButtonFactory* InputSubsystem::GetGCButtons() const { + return impl->gcbuttons.get(); +} + +UDPMotionFactory* InputSubsystem::GetUDPMotions() { + return impl->udpmotion.get(); +} + +const UDPMotionFactory* InputSubsystem::GetUDPMotions() const { + return impl->udpmotion.get(); +} + +UDPTouchFactory* InputSubsystem::GetUDPTouch() { + return impl->udptouch.get(); +} + +const UDPTouchFactory* InputSubsystem::GetUDPTouch() const { + return impl->udptouch.get(); +} + +void InputSubsystem::ReloadInputDevices() { + if (!impl->udp) { + return; + } + impl->udp->ReloadUDPClient(); +} + +std::vector<std::unique_ptr<Polling::DevicePoller>> InputSubsystem::GetPollers( + Polling::DeviceType type) const { +#ifdef HAVE_SDL2 + return impl->sdl->GetPollers(type); +#else + return {}; +#endif } std::string GenerateKeyboardParam(int key_code) { @@ -79,18 +261,4 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, }; return circle_pad_param.Serialize(); } - -namespace Polling { - -std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { - std::vector<std::unique_ptr<DevicePoller>> pollers; - -#ifdef HAVE_SDL2 - pollers = sdl->GetPollers(type); -#endif - - return pollers; -} - -} // namespace Polling } // namespace InputCommon diff --git a/src/input_common/main.h b/src/input_common/main.h index 77a0ce90b..dded3f1ef 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -6,40 +6,29 @@ #include <memory> #include <string> +#include <unordered_map> #include <vector> namespace Common { class ParamPackage; } -namespace InputCommon { - -/// Initializes and registers all built-in input device factories. -void Init(); - -/// Deregisters all built-in input device factories and shuts them down. -void Shutdown(); - -class Keyboard; - -/// Gets the keyboard button device factory. -Keyboard* GetKeyboard(); - -class MotionEmu; - -/// Gets the motion emulation factory. -MotionEmu* GetMotionEmu(); +namespace Settings::NativeAnalog { +enum Values : int; +} -/// Generates a serialized param package for creating a keyboard button device -std::string GenerateKeyboardParam(int key_code); +namespace Settings::NativeButton { +enum Values : int; +} -/// Generates a serialized param package for creating an analog device taking input from keyboard -std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, - int key_modifier, float modifier_scale); +namespace Settings::NativeMotion { +enum Values : int; +} +namespace InputCommon { namespace Polling { -enum class DeviceType { Button, Analog }; +enum class DeviceType { Button, AnalogPreferred, Motion }; /** * A class that can be used to get inputs from an input device like controllers without having to @@ -49,7 +38,9 @@ class DevicePoller { public: virtual ~DevicePoller() = default; /// Setup and start polling for inputs, should be called before GetNextInput - virtual void Start() = 0; + /// If a device_id is provided, events should be filtered to only include events from this + /// device id + virtual void Start(const std::string& device_id = "") = 0; /// Stop polling virtual void Stop() = 0; /** @@ -59,8 +50,110 @@ public: */ virtual Common::ParamPackage GetNextInput() = 0; }; - -// Get all DevicePoller from all backends for a specific device type -std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type); } // namespace Polling + +class GCAnalogFactory; +class GCButtonFactory; +class UDPMotionFactory; +class UDPTouchFactory; +class Keyboard; +class MotionEmu; + +/** + * Given a ParamPackage for a Device returned from `GetInputDevices`, attempt to get the default + * mapping for the device. This is currently only implemented for the SDL backend devices. + */ +using AnalogMapping = std::unordered_map<Settings::NativeAnalog::Values, Common::ParamPackage>; +using ButtonMapping = std::unordered_map<Settings::NativeButton::Values, Common::ParamPackage>; +using MotionMapping = std::unordered_map<Settings::NativeMotion::Values, Common::ParamPackage>; + +class InputSubsystem { +public: + explicit InputSubsystem(); + ~InputSubsystem(); + + InputSubsystem(const InputSubsystem&) = delete; + InputSubsystem& operator=(const InputSubsystem&) = delete; + + InputSubsystem(InputSubsystem&&) = delete; + InputSubsystem& operator=(InputSubsystem&&) = delete; + + /// Initializes and registers all built-in input device factories. + void Initialize(); + + /// Unregisters all built-in input device factories and shuts them down. + void Shutdown(); + + /// Retrieves the underlying keyboard device. + [[nodiscard]] Keyboard* GetKeyboard(); + + /// Retrieves the underlying keyboard device. + [[nodiscard]] const Keyboard* GetKeyboard() const; + + /// Retrieves the underlying motion emulation factory. + [[nodiscard]] MotionEmu* GetMotionEmu(); + + /// Retrieves the underlying motion emulation factory. + [[nodiscard]] const MotionEmu* GetMotionEmu() const; + + /** + * Returns all available input devices that this Factory can create a new device with. + * Each returned ParamPackage should have a `display` field used for display, a class field for + * backends to determine if this backend is meant to service the request and any other + * information needed to identify this in the backend later. + */ + [[nodiscard]] std::vector<Common::ParamPackage> GetInputDevices() const; + + /// Retrieves the analog mappings for the given device. + [[nodiscard]] AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& device) const; + + /// Retrieves the button mappings for the given device. + [[nodiscard]] ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& device) const; + + /// Retrieves the motion mappings for the given device. + [[nodiscard]] MotionMapping GetMotionMappingForDevice(const Common::ParamPackage& device) const; + + /// Retrieves the underlying GameCube analog handler. + [[nodiscard]] GCAnalogFactory* GetGCAnalogs(); + + /// Retrieves the underlying GameCube analog handler. + [[nodiscard]] const GCAnalogFactory* GetGCAnalogs() const; + + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] GCButtonFactory* GetGCButtons(); + + /// Retrieves the underlying GameCube button handler. + [[nodiscard]] const GCButtonFactory* GetGCButtons() const; + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] UDPMotionFactory* GetUDPMotions(); + + /// Retrieves the underlying udp motion handler. + [[nodiscard]] const UDPMotionFactory* GetUDPMotions() const; + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] UDPTouchFactory* GetUDPTouch(); + + /// Retrieves the underlying udp touch handler. + [[nodiscard]] const UDPTouchFactory* GetUDPTouch() const; + + /// Reloads the input devices + void ReloadInputDevices(); + + /// Get all DevicePoller from all backends for a specific device type + [[nodiscard]] std::vector<std::unique_ptr<Polling::DevicePoller>> GetPollers( + Polling::DeviceType type) const; + +private: + struct Impl; + std::unique_ptr<Impl> impl; +}; + +/// Generates a serialized param package for creating a keyboard button device +std::string GenerateKeyboardParam(int key_code); + +/// Generates a serialized param package for creating an analog device taking input from keyboard +std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, int key_right, + int key_modifier, float modifier_scale); + } // namespace InputCommon diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp index 868251628..69fd3c1d2 100644 --- a/src/input_common/motion_emu.cpp +++ b/src/input_common/motion_emu.cpp @@ -56,7 +56,7 @@ public: is_tilting = false; } - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() { + Input::MotionStatus GetStatus() { std::lock_guard guard{status_mutex}; return status; } @@ -76,7 +76,7 @@ private: Common::Event shutdown_event; - std::tuple<Common::Vec3<float>, Common::Vec3<float>> status; + Input::MotionStatus status; std::mutex status_mutex; // Note: always keep the thread declaration at the end so that other objects are initialized @@ -113,10 +113,19 @@ private: gravity = QuaternionRotate(inv_q, gravity); angular_rate = QuaternionRotate(inv_q, angular_rate); + // TODO: Calculate the correct rotation vector and orientation matrix + const auto matrix4x4 = q.ToMatrix(); + const auto rotation = Common::MakeVec(0.0f, 0.0f, 0.0f); + const std::array orientation{ + Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), + Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), + Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10]), + }; + // Update the sensor state { std::lock_guard guard{status_mutex}; - status = std::make_tuple(gravity, angular_rate); + status = std::make_tuple(gravity, angular_rate, rotation, orientation); } } } @@ -131,7 +140,7 @@ public: device = std::make_shared<MotionEmuDevice>(update_millisecond, sensitivity); } - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { + Input::MotionStatus GetStatus() const override { return device->GetStatus(); } @@ -145,7 +154,7 @@ std::unique_ptr<Input::MotionDevice> MotionEmu::Create(const Common::ParamPackag // Previously created device is disconnected here. Having two motion devices for 3DS is not // expected. current_device = device_wrapper->device; - return std::move(device_wrapper); + return device_wrapper; } void MotionEmu::BeginTilt(int x, int y) { diff --git a/src/input_common/motion_input.cpp b/src/input_common/motion_input.cpp new file mode 100644 index 000000000..22a849866 --- /dev/null +++ b/src/input_common/motion_input.cpp @@ -0,0 +1,181 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#include "common/math_util.h" +#include "input_common/motion_input.h" + +namespace InputCommon { + +MotionInput::MotionInput(f32 new_kp, f32 new_ki, f32 new_kd) + : kp(new_kp), ki(new_ki), kd(new_kd), quat{{0, 0, -1}, 0} {} + +void MotionInput::SetAcceleration(const Common::Vec3f& acceleration) { + accel = acceleration; +} + +void MotionInput::SetGyroscope(const Common::Vec3f& gyroscope) { + gyro = gyroscope - gyro_drift; + if (gyro.Length2() < gyro_threshold) { + gyro = {}; + } +} + +void MotionInput::SetQuaternion(const Common::Quaternion<f32>& quaternion) { + quat = quaternion; +} + +void MotionInput::SetGyroDrift(const Common::Vec3f& drift) { + gyro_drift = drift; +} + +void MotionInput::SetGyroThreshold(f32 threshold) { + gyro_threshold = threshold; +} + +void MotionInput::EnableReset(bool reset) { + reset_enabled = reset; +} + +void MotionInput::ResetRotations() { + rotations = {}; +} + +bool MotionInput::IsMoving(f32 sensitivity) const { + return gyro.Length() >= sensitivity || accel.Length() <= 0.9f || accel.Length() >= 1.1f; +} + +bool MotionInput::IsCalibrated(f32 sensitivity) const { + return real_error.Length() < sensitivity; +} + +void MotionInput::UpdateRotation(u64 elapsed_time) { + const f32 sample_period = elapsed_time / 1000000.0f; + if (sample_period > 0.1f) { + return; + } + rotations += gyro * sample_period; +} + +void MotionInput::UpdateOrientation(u64 elapsed_time) { + if (!IsCalibrated(0.1f)) { + ResetOrientation(); + } + // Short name local variable for readability + f32 q1 = quat.w; + f32 q2 = quat.xyz[0]; + f32 q3 = quat.xyz[1]; + f32 q4 = quat.xyz[2]; + const f32 sample_period = elapsed_time / 1000000.0f; + + // ignore invalid elapsed time + if (sample_period > 0.1f) { + return; + } + + const auto normal_accel = accel.Normalized(); + auto rad_gyro = gyro * Common::PI * 2; + const f32 swap = rad_gyro.x; + rad_gyro.x = rad_gyro.y; + rad_gyro.y = -swap; + rad_gyro.z = -rad_gyro.z; + + // Ignore drift correction if acceleration is not reliable + if (accel.Length() >= 0.75f && accel.Length() <= 1.25f) { + const f32 ax = -normal_accel.x; + const f32 ay = normal_accel.y; + const f32 az = -normal_accel.z; + + // Estimated direction of gravity + const f32 vx = 2.0f * (q2 * q4 - q1 * q3); + const f32 vy = 2.0f * (q1 * q2 + q3 * q4); + const f32 vz = q1 * q1 - q2 * q2 - q3 * q3 + q4 * q4; + + // Error is cross product between estimated direction and measured direction of gravity + const Common::Vec3f new_real_error = {az * vx - ax * vz, ay * vz - az * vy, + ax * vy - ay * vx}; + + derivative_error = new_real_error - real_error; + real_error = new_real_error; + + // Prevent integral windup + if (ki != 0.0f && !IsCalibrated(0.05f)) { + integral_error += real_error; + } else { + integral_error = {}; + } + + // Apply feedback terms + rad_gyro += kp * real_error; + rad_gyro += ki * integral_error; + rad_gyro += kd * derivative_error; + } + + const f32 gx = rad_gyro.y; + const f32 gy = rad_gyro.x; + const f32 gz = rad_gyro.z; + + // Integrate rate of change of quaternion + const f32 pa = q2; + const f32 pb = q3; + const f32 pc = q4; + q1 = q1 + (-q2 * gx - q3 * gy - q4 * gz) * (0.5f * sample_period); + q2 = pa + (q1 * gx + pb * gz - pc * gy) * (0.5f * sample_period); + q3 = pb + (q1 * gy - pa * gz + pc * gx) * (0.5f * sample_period); + q4 = pc + (q1 * gz + pa * gy - pb * gx) * (0.5f * sample_period); + + quat.w = q1; + quat.xyz[0] = q2; + quat.xyz[1] = q3; + quat.xyz[2] = q4; + quat = quat.Normalized(); +} + +std::array<Common::Vec3f, 3> MotionInput::GetOrientation() const { + const Common::Quaternion<float> quad{ + .xyz = {-quat.xyz[1], -quat.xyz[0], -quat.w}, + .w = -quat.xyz[2], + }; + const std::array<float, 16> matrix4x4 = quad.ToMatrix(); + + return {Common::Vec3f(matrix4x4[0], matrix4x4[1], -matrix4x4[2]), + Common::Vec3f(matrix4x4[4], matrix4x4[5], -matrix4x4[6]), + Common::Vec3f(-matrix4x4[8], -matrix4x4[9], matrix4x4[10])}; +} + +Common::Vec3f MotionInput::GetAcceleration() const { + return accel; +} + +Common::Vec3f MotionInput::GetGyroscope() const { + return gyro; +} + +Common::Quaternion<f32> MotionInput::GetQuaternion() const { + return quat; +} + +Common::Vec3f MotionInput::GetRotations() const { + return rotations; +} + +void MotionInput::ResetOrientation() { + if (!reset_enabled) { + return; + } + if (!IsMoving(0.5f) && accel.z <= -0.9f) { + ++reset_counter; + if (reset_counter > 900) { + // TODO: calculate quaternion from gravity vector + quat.w = 0; + quat.xyz[0] = 0; + quat.xyz[1] = 0; + quat.xyz[2] = -1; + integral_error = {}; + reset_counter = 0; + } + } else { + reset_counter = 0; + } +} +} // namespace InputCommon diff --git a/src/input_common/motion_input.h b/src/input_common/motion_input.h new file mode 100644 index 000000000..54b4439d9 --- /dev/null +++ b/src/input_common/motion_input.h @@ -0,0 +1,68 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included + +#pragma once + +#include "common/common_types.h" +#include "common/quaternion.h" +#include "common/vector_math.h" + +namespace InputCommon { + +class MotionInput { +public: + MotionInput(f32 new_kp, f32 new_ki, f32 new_kd); + + MotionInput(const MotionInput&) = default; + MotionInput& operator=(const MotionInput&) = default; + + MotionInput(MotionInput&&) = default; + MotionInput& operator=(MotionInput&&) = default; + + void SetAcceleration(const Common::Vec3f& acceleration); + void SetGyroscope(const Common::Vec3f& acceleration); + void SetQuaternion(const Common::Quaternion<f32>& quaternion); + void SetGyroDrift(const Common::Vec3f& drift); + void SetGyroThreshold(f32 threshold); + + void EnableReset(bool reset); + void ResetRotations(); + + void UpdateRotation(u64 elapsed_time); + void UpdateOrientation(u64 elapsed_time); + + std::array<Common::Vec3f, 3> GetOrientation() const; + Common::Vec3f GetAcceleration() const; + Common::Vec3f GetGyroscope() const; + Common::Vec3f GetRotations() const; + Common::Quaternion<f32> GetQuaternion() const; + + bool IsMoving(f32 sensitivity) const; + bool IsCalibrated(f32 sensitivity) const; + +private: + void ResetOrientation(); + + // PID constants + const f32 kp; + const f32 ki; + const f32 kd; + + // PID errors + Common::Vec3f real_error; + Common::Vec3f integral_error; + Common::Vec3f derivative_error; + + Common::Quaternion<f32> quat; + Common::Vec3f rotations; + Common::Vec3f accel; + Common::Vec3f gyro; + Common::Vec3f gyro_drift; + + f32 gyro_threshold = 0.0f; + u32 reset_counter = 0; + bool reset_enabled = true; +}; + +} // namespace InputCommon diff --git a/src/input_common/sdl/sdl.h b/src/input_common/sdl/sdl.h index 5306daa70..f3554be9a 100644 --- a/src/input_common/sdl/sdl.h +++ b/src/input_common/sdl/sdl.h @@ -6,6 +6,7 @@ #include <memory> #include <vector> +#include "common/param_package.h" #include "input_common/main.h" namespace InputCommon::Polling { @@ -22,14 +23,24 @@ public: /// Unregisters SDL device factories and shut them down. virtual ~State() = default; - virtual Pollers GetPollers(Polling::DeviceType type) = 0; + virtual Pollers GetPollers(Polling::DeviceType type) { + return {}; + } + + virtual std::vector<Common::ParamPackage> GetInputDevices() { + return {}; + } + + virtual ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage&) { + return {}; + } + virtual AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage&) { + return {}; + } }; class NullState : public State { public: - Pollers GetPollers(Polling::DeviceType type) override { - return {}; - } }; std::unique_ptr<State> Init(); diff --git a/src/input_common/sdl/sdl_impl.cpp b/src/input_common/sdl/sdl_impl.cpp index 675b477fa..a9e676f4b 100644 --- a/src/input_common/sdl/sdl_impl.cpp +++ b/src/input_common/sdl/sdl_impl.cpp @@ -3,10 +3,13 @@ // Refer to the license.txt file included. #include <algorithm> +#include <array> #include <atomic> #include <cmath> #include <functional> #include <mutex> +#include <optional> +#include <sstream> #include <string> #include <thread> #include <tuple> @@ -15,15 +18,16 @@ #include <vector> #include <SDL.h> #include "common/logging/log.h" -#include "common/math_util.h" #include "common/param_package.h" #include "common/threadsafe_queue.h" #include "core/frontend/input.h" #include "input_common/sdl/sdl_impl.h" +#include "input_common/settings.h" namespace InputCommon::SDL { -static std::string GetGUID(SDL_Joystick* joystick) { +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)); @@ -31,7 +35,8 @@ static std::string GetGUID(SDL_Joystick* joystick) { } /// Creates a ParamPackage from an SDL_Event that can directly be used to create a ButtonDevice -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event); +} // Anonymous namespace static int SDLEventWatcher(void* user_data, SDL_Event* event) { auto* const sdl_state = static_cast<SDLState*>(user_data); @@ -48,8 +53,10 @@ static int SDLEventWatcher(void* user_data, SDL_Event* event) { class SDLJoystick { public: - SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick) - : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose} {} + SDLJoystick(std::string guid_, int port_, SDL_Joystick* joystick, + SDL_GameController* gamecontroller) + : guid{std::move(guid_)}, port{port_}, sdl_joystick{joystick, &SDL_JoystickClose}, + sdl_controller{gamecontroller, &SDL_GameControllerClose} {} void SetButton(int button, bool value) { std::lock_guard lock{mutex}; @@ -66,14 +73,14 @@ public: state.axes.insert_or_assign(axis, value); } - float GetAxis(int axis) const { + float GetAxis(int axis, float range) const { std::lock_guard lock{mutex}; - return state.axes.at(axis) / 32767.0f; + return state.axes.at(axis) / (32767.0f * range); } - std::tuple<float, float> GetAnalog(int axis_x, int axis_y) const { - float x = GetAxis(axis_x); - float y = GetAxis(axis_y); + std::tuple<float, float> GetAnalog(int axis_x, int axis_y, float range) const { + float x = GetAxis(axis_x, range); + float y = GetAxis(axis_y, range); y = -y; // 3DS uses an y-axis inverse from SDL // Make sure the coordinates are in the unit circle, @@ -115,10 +122,15 @@ public: return sdl_joystick.get(); } - void SetSDLJoystick(SDL_Joystick* joystick) { + void SetSDLJoystick(SDL_Joystick* joystick, SDL_GameController* controller) { + sdl_controller.reset(controller); sdl_joystick.reset(joystick); } + SDL_GameController* GetSDLGameController() const { + return sdl_controller.get(); + } + private: struct State { std::unordered_map<int, bool> buttons; @@ -128,6 +140,7 @@ private: std::string guid; int port; std::unique_ptr<SDL_Joystick, decltype(&SDL_JoystickClose)> sdl_joystick; + std::unique_ptr<SDL_GameController, decltype(&SDL_GameControllerClose)> sdl_controller; mutable std::mutex mutex; }; @@ -136,18 +149,19 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickByGUID(const std::string& g const auto it = joystick_map.find(guid); if (it != joystick_map.end()) { while (it->second.size() <= static_cast<std::size_t>(port)) { - auto joystick = - std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), nullptr); + auto joystick = std::make_shared<SDLJoystick>(guid, static_cast<int>(it->second.size()), + nullptr, nullptr); it->second.emplace_back(std::move(joystick)); } return it->second[port]; } - auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr); + auto joystick = std::make_shared<SDLJoystick>(guid, 0, nullptr, nullptr); return joystick_map[guid].emplace_back(std::move(joystick)); } std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_id) { auto sdl_joystick = SDL_JoystickFromInstanceID(sdl_id); + auto sdl_controller = SDL_GameControllerFromInstanceID(sdl_id); const std::string guid = GetGUID(sdl_joystick); std::lock_guard lock{joystick_map_mutex}; @@ -171,23 +185,27 @@ std::shared_ptr<SDLJoystick> SDLState::GetSDLJoystickBySDLID(SDL_JoystickID sdl_ }); if (nullptr_it != map_it->second.end()) { // ... and map it - (*nullptr_it)->SetSDLJoystick(sdl_joystick); + (*nullptr_it)->SetSDLJoystick(sdl_joystick, sdl_controller); return *nullptr_it; } // There is no SDLJoystick without a mapped SDL_Joystick // Create a new SDLJoystick const int port = static_cast<int>(map_it->second.size()); - auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); + auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_controller); return map_it->second.emplace_back(std::move(joystick)); } - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_controller); return joystick_map[guid].emplace_back(std::move(joystick)); } void SDLState::InitJoystick(int joystick_index) { SDL_Joystick* sdl_joystick = SDL_JoystickOpen(joystick_index); + SDL_GameController* sdl_gamecontroller = nullptr; + if (SDL_IsGameController(joystick_index)) { + sdl_gamecontroller = SDL_GameControllerOpen(joystick_index); + } if (!sdl_joystick) { LOG_ERROR(Input, "failed to open joystick {}", joystick_index); return; @@ -196,7 +214,7 @@ void SDLState::InitJoystick(int joystick_index) { std::lock_guard lock{joystick_map_mutex}; if (joystick_map.find(guid) == joystick_map.end()) { - auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick); + auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -205,11 +223,11 @@ void SDLState::InitJoystick(int joystick_index) { joystick_guid_list.begin(), joystick_guid_list.end(), [](const std::shared_ptr<SDLJoystick>& joystick) { return !joystick->GetSDLJoystick(); }); if (it != joystick_guid_list.end()) { - (*it)->SetSDLJoystick(sdl_joystick); + (*it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); return; } const int port = static_cast<int>(joystick_guid_list.size()); - auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick); + auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); joystick_guid_list.emplace_back(std::move(joystick)); } @@ -231,7 +249,7 @@ void SDLState::CloseJoystick(SDL_Joystick* sdl_joystick) { // Destruct SDL_Joystick outside the lock guard because SDL can internally call the // event callback which locks the mutex again. - joystick->SetSDLJoystick(nullptr); + joystick->SetSDLJoystick(nullptr, nullptr); } void SDLState::HandleGameControllerEvent(const SDL_Event& event) { @@ -313,7 +331,7 @@ public: trigger_if_greater(trigger_if_greater_) {} bool GetStatus() const override { - const float axis_value = joystick->GetAxis(axis); + const float axis_value = joystick->GetAxis(axis, 1.0f); if (trigger_if_greater) { return axis_value > threshold; } @@ -329,22 +347,24 @@ private: class SDLAnalog final : public Input::AnalogDevice { public: - SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_) - : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_) {} + SDLAnalog(std::shared_ptr<SDLJoystick> joystick_, int axis_x_, int axis_y_, float deadzone_, + float range_) + : joystick(std::move(joystick_)), axis_x(axis_x_), axis_y(axis_y_), deadzone(deadzone_), + range(range_) {} std::tuple<float, float> GetStatus() const override { - const auto [x, y] = joystick->GetAnalog(axis_x, axis_y); + const auto [x, y] = joystick->GetAnalog(axis_x, axis_y, range); const float r = std::sqrt((x * x) + (y * y)); if (r > deadzone) { return std::make_tuple(x / r * (r - deadzone) / (1 - deadzone), y / r * (r - deadzone) / (1 - deadzone)); } - return std::make_tuple<float, float>(0.0f, 0.0f); + return {}; } bool GetAnalogDirectionStatus(Input::AnalogDirection direction) const override { const auto [x, y] = GetStatus(); - const float directional_deadzone = 0.4f; + const float directional_deadzone = 0.5f; switch (direction) { case Input::AnalogDirection::RIGHT: return x > directional_deadzone; @@ -363,6 +383,7 @@ private: const int axis_x; const int axis_y; const float deadzone; + const float range; }; /// A button device factory that creates button devices from SDL joystick @@ -457,14 +478,14 @@ public: const int port = params.Get("port", 0); const int axis_x = params.Get("axis_x", 0); const int axis_y = params.Get("axis_y", 1); - const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, .99f); - + const float deadzone = std::clamp(params.Get("deadzone", 0.0f), 0.0f, 1.0f); + const float range = std::clamp(params.Get("range", 1.0f), 0.50f, 1.50f); auto joystick = state.GetSDLJoystickByGUID(guid, port); // This is necessary so accessing GetAxis with axis_x and axis_y won't crash joystick->SetAxis(axis_x, 0); joystick->SetAxis(axis_y, 0); - return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone); + return std::make_unique<SDLAnalog>(joystick, axis_x, axis_y, deadzone, range); } private: @@ -473,8 +494,10 @@ private: SDLState::SDLState() { using namespace Input; - RegisterFactory<ButtonDevice>("sdl", std::make_shared<SDLButtonFactory>(*this)); - RegisterFactory<AnalogDevice>("sdl", std::make_shared<SDLAnalogFactory>(*this)); + analog_factory = std::make_shared<SDLAnalogFactory>(*this); + button_factory = std::make_shared<SDLButtonFactory>(*this); + RegisterFactory<AnalogDevice>("sdl", analog_factory); + RegisterFactory<ButtonDevice>("sdl", button_factory); // If the frontend is going to manage the event loop, then we dont start one here start_thread = !SDL_WasInit(SDL_INIT_JOYSTICK); @@ -482,6 +505,7 @@ SDLState::SDLState() { LOG_CRITICAL(Input, "SDL_Init(SDL_INIT_JOYSTICK) failed with: {}", SDL_GetError()); return; } + has_gamecontroller = SDL_InitSubSystem(SDL_INIT_GAMECONTROLLER); if (SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1") == SDL_FALSE) { LOG_ERROR(Input, "Failed to set hint for background events with: {}", SDL_GetError()); } @@ -494,7 +518,7 @@ SDLState::SDLState() { using namespace std::chrono_literals; while (initialized) { SDL_PumpEvents(); - std::this_thread::sleep_for(10ms); + std::this_thread::sleep_for(5ms); } }); } @@ -520,65 +544,230 @@ SDLState::~SDLState() { } } -static Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { +std::vector<Common::ParamPackage> SDLState::GetInputDevices() { + std::scoped_lock lock(joystick_map_mutex); + std::vector<Common::ParamPackage> devices; + for (const auto& [key, value] : joystick_map) { + for (const auto& joystick : value) { + auto joy = joystick->GetSDLJoystick(); + if (auto controller = joystick->GetSDLGameController()) { + std::string name = + fmt::format("{} {}", SDL_GameControllerName(controller), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"class", "sdl"}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } else if (joy) { + std::string name = fmt::format("{} {}", SDL_JoystickName(joy), joystick->GetPort()); + devices.emplace_back(Common::ParamPackage{ + {"class", "sdl"}, + {"display", std::move(name)}, + {"guid", joystick->GetGUID()}, + {"port", std::to_string(joystick->GetPort())}, + }); + } + } + } + return devices; +} + +namespace { +Common::ParamPackage BuildAnalogParamPackageForButton(int port, std::string guid, u8 axis, + float value = 0.1f) { + Common::ParamPackage params({{"engine", "sdl"}}); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("axis", axis); + if (value > 0) { + params.Set("direction", "+"); + params.Set("threshold", "0.5"); + } else { + params.Set("direction", "-"); + params.Set("threshold", "-0.5"); + } + return params; +} + +Common::ParamPackage BuildButtonParamPackageForButton(int port, std::string guid, u8 button) { Common::ParamPackage params({{"engine", "sdl"}}); + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("button", button); + return params; +} + +Common::ParamPackage BuildHatParamPackageForButton(int port, std::string guid, u8 hat, u8 value) { + Common::ParamPackage params({{"engine", "sdl"}}); + + params.Set("port", port); + params.Set("guid", std::move(guid)); + params.Set("hat", hat); + switch (value) { + case SDL_HAT_UP: + params.Set("direction", "up"); + break; + case SDL_HAT_DOWN: + params.Set("direction", "down"); + break; + case SDL_HAT_LEFT: + params.Set("direction", "left"); + break; + case SDL_HAT_RIGHT: + params.Set("direction", "right"); + break; + default: + return {}; + } + return params; +} +Common::ParamPackage SDLEventToButtonParamPackage(SDLState& state, const SDL_Event& event) { switch (event.type) { case SDL_JOYAXISMOTION: { const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis", event.jaxis.axis); - if (event.jaxis.value > 0) { - params.Set("direction", "+"); - params.Set("threshold", "0.5"); - } else { - params.Set("direction", "-"); - params.Set("threshold", "-0.5"); - } - break; + return BuildAnalogParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jaxis.axis, event.jaxis.value); } case SDL_JOYBUTTONUP: { const auto joystick = state.GetSDLJoystickBySDLID(event.jbutton.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("button", event.jbutton.button); - break; + return BuildButtonParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jbutton.button); } case SDL_JOYHATMOTION: { const auto joystick = state.GetSDLJoystickBySDLID(event.jhat.which); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("hat", event.jhat.hat); - switch (event.jhat.value) { - case SDL_HAT_UP: - params.Set("direction", "up"); - break; - case SDL_HAT_DOWN: - params.Set("direction", "down"); - break; - case SDL_HAT_LEFT: - params.Set("direction", "left"); - break; - case SDL_HAT_RIGHT: - params.Set("direction", "right"); - break; - default: - return {}; - } - break; + return BuildHatParamPackageForButton(joystick->GetPort(), joystick->GetGUID(), + event.jhat.hat, event.jhat.value); } } + return {}; +} + +Common::ParamPackage BuildParamPackageForBinding(int port, const std::string& guid, + const SDL_GameControllerButtonBind& binding) { + switch (binding.bindType) { + case SDL_CONTROLLER_BINDTYPE_AXIS: + return BuildAnalogParamPackageForButton(port, guid, binding.value.axis); + case SDL_CONTROLLER_BINDTYPE_BUTTON: + return BuildButtonParamPackageForButton(port, guid, binding.value.button); + case SDL_CONTROLLER_BINDTYPE_HAT: + return BuildHatParamPackageForButton(port, guid, binding.value.hat.hat, + binding.value.hat.hat_mask); + } + return {}; +} + +Common::ParamPackage BuildParamPackageForAnalog(int port, const std::string& guid, int axis_x, + int axis_y) { + Common::ParamPackage params; + params.Set("engine", "sdl"); + params.Set("port", port); + params.Set("guid", guid); + params.Set("axis_x", axis_x); + params.Set("axis_y", axis_y); return params; } +} // Anonymous namespace -namespace Polling { +ButtonMapping SDLState::GetButtonMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + auto* controller = joystick->GetSDLGameController(); + if (controller == nullptr) { + return {}; + } + + // This list is missing ZL/ZR since those are not considered buttons in SDL GameController. + // We will add those afterwards + // This list also excludes Screenshot since theres not really a mapping for that + using ButtonBindings = + std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerButton>, 17>; + static constexpr ButtonBindings switch_to_sdl_button{{ + {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}, + }}; + + // Add the missing bindings for ZL/ZR + using ZBindings = + std::array<std::pair<Settings::NativeButton::Values, SDL_GameControllerAxis>, 2>; + static constexpr ZBindings switch_to_sdl_axis{{ + {Settings::NativeButton::ZL, SDL_CONTROLLER_AXIS_TRIGGERLEFT}, + {Settings::NativeButton::ZR, SDL_CONTROLLER_AXIS_TRIGGERRIGHT}, + }}; + + ButtonMapping mapping; + mapping.reserve(switch_to_sdl_button.size() + switch_to_sdl_axis.size()); + + 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; +} +AnalogMapping SDLState::GetAnalogMappingForDevice(const Common::ParamPackage& params) { + if (!params.Has("guid") || !params.Has("port")) { + return {}; + } + const auto joystick = GetSDLJoystickByGUID(params.Get("guid", ""), params.Get("port", 0)); + 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); + mapping.insert_or_assign(Settings::NativeAnalog::LStick, + BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + binding_left_x.value.axis, + binding_left_y.value.axis)); + const auto& binding_right_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX); + const auto& binding_right_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY); + mapping.insert_or_assign(Settings::NativeAnalog::RStick, + BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + binding_right_x.value.axis, + binding_right_y.value.axis)); + return mapping; +} + +namespace Polling { class SDLPoller : public InputCommon::Polling::DevicePoller { public: explicit SDLPoller(SDLState& state_) : state(state_) {} - void Start() override { + void Start(const std::string& device_id) override { state.event_queue.Clear(); state.polling = true; } @@ -598,71 +787,106 @@ public: Common::ParamPackage GetNextInput() override { SDL_Event event; while (state.event_queue.Pop(event)) { - switch (event.type) { - case SDL_JOYAXISMOTION: - if (std::abs(event.jaxis.value / 32767.0) < 0.5) { - break; - } - [[fallthrough]]; - case SDL_JOYBUTTONUP: - case SDL_JOYHATMOTION: - return SDLEventToButtonParamPackage(state, event); + const auto package = FromEvent(event); + if (package) { + return *package; } } return {}; } + [[nodiscard]] std::optional<Common::ParamPackage> FromEvent(const SDL_Event& event) const { + switch (event.type) { + case SDL_JOYAXISMOTION: + if (std::abs(event.jaxis.value / 32767.0) < 0.5) { + break; + } + [[fallthrough]]; + case SDL_JOYBUTTONUP: + case SDL_JOYHATMOTION: + return {SDLEventToButtonParamPackage(state, event)}; + } + return std::nullopt; + } }; -class SDLAnalogPoller final : public SDLPoller { +/** + * Attempts to match the press to a controller joy axis (left/right stick) and if a match + * isn't found, checks if the event matches anything from SDLButtonPoller and uses that + * instead + */ +class SDLAnalogPreferredPoller final : public SDLPoller { public: - explicit SDLAnalogPoller(SDLState& state_) : SDLPoller(state_) {} - - void Start() override { - SDLPoller::Start(); + explicit SDLAnalogPreferredPoller(SDLState& state_) + : SDLPoller(state_), button_poller(state_) {} + void Start(const std::string& device_id) override { + SDLPoller::Start(device_id); + // Load the game controller // Reset stored axes analog_x_axis = -1; analog_y_axis = -1; - analog_axes_joystick = -1; } Common::ParamPackage GetNextInput() override { SDL_Event event; while (state.event_queue.Pop(event)) { - if (event.type != SDL_JOYAXISMOTION || std::abs(event.jaxis.value / 32767.0) < 0.5) { + // Filter out axis events that are below a threshold + if (event.type == SDL_JOYAXISMOTION && std::abs(event.jaxis.value / 32767.0) < 0.5) { continue; } - // An analog device needs two axes, so we need to store the axis for later and wait for - // a second SDL event. The axes also must be from the same joystick. - const int axis = event.jaxis.axis; - if (analog_x_axis == -1) { - analog_x_axis = axis; - analog_axes_joystick = event.jaxis.which; - } else if (analog_y_axis == -1 && analog_x_axis != axis && - analog_axes_joystick == event.jaxis.which) { - analog_y_axis = axis; + // Simplify controller config by testing if game controller support is enabled. + if (event.type == SDL_JOYAXISMOTION) { + const auto axis = event.jaxis.axis; + const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); + const auto controller = joystick->GetSDLGameController(); + if (controller) { + const auto axis_left_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTX) + .value.axis; + const auto axis_left_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_LEFTY) + .value.axis; + const auto axis_right_x = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTX) + .value.axis; + const auto axis_right_y = + SDL_GameControllerGetBindForAxis(controller, SDL_CONTROLLER_AXIS_RIGHTY) + .value.axis; + + if (axis == axis_left_x || axis == axis_left_y) { + analog_x_axis = axis_left_x; + analog_y_axis = axis_left_y; + break; + } else if (axis == axis_right_x || axis == axis_right_y) { + analog_x_axis = axis_right_x; + analog_y_axis = axis_right_y; + break; + } + } + } + + // If the press wasn't accepted as a joy axis, check for a button press + auto button_press = button_poller.FromEvent(event); + if (button_press) { + return *button_press; } } - Common::ParamPackage params; + if (analog_x_axis != -1 && analog_y_axis != -1) { const auto joystick = state.GetSDLJoystickBySDLID(event.jaxis.which); - params.Set("engine", "sdl"); - params.Set("port", joystick->GetPort()); - params.Set("guid", joystick->GetGUID()); - params.Set("axis_x", analog_x_axis); - params.Set("axis_y", analog_y_axis); + auto params = BuildParamPackageForAnalog(joystick->GetPort(), joystick->GetGUID(), + analog_x_axis, analog_y_axis); analog_x_axis = -1; analog_y_axis = -1; - analog_axes_joystick = -1; return params; } - return params; + return {}; } private: int analog_x_axis = -1; int analog_y_axis = -1; - SDL_JoystickID analog_axes_joystick = -1; + SDLButtonPoller button_poller; }; } // namespace Polling @@ -670,8 +894,8 @@ SDLState::Pollers SDLState::GetPollers(InputCommon::Polling::DeviceType type) { Pollers pollers; switch (type) { - case InputCommon::Polling::DeviceType::Analog: - pollers.emplace_back(std::make_unique<Polling::SDLAnalogPoller>(*this)); + case InputCommon::Polling::DeviceType::AnalogPreferred: + pollers.emplace_back(std::make_unique<Polling::SDLAnalogPreferredPoller>(*this)); break; case InputCommon::Polling::DeviceType::Button: pollers.emplace_back(std::make_unique<Polling::SDLButtonPoller>(*this)); diff --git a/src/input_common/sdl/sdl_impl.h b/src/input_common/sdl/sdl_impl.h index 606a32c5b..bd19ba61d 100644 --- a/src/input_common/sdl/sdl_impl.h +++ b/src/input_common/sdl/sdl_impl.h @@ -50,6 +50,11 @@ public: std::atomic<bool> polling = false; Common::SPSCQueue<SDL_Event> event_queue; + std::vector<Common::ParamPackage> GetInputDevices() override; + + ButtonMapping GetButtonMappingForDevice(const Common::ParamPackage& params) override; + AnalogMapping GetAnalogMappingForDevice(const Common::ParamPackage& params) override; + private: void InitJoystick(int joystick_index); void CloseJoystick(SDL_Joystick* sdl_joystick); @@ -57,6 +62,9 @@ private: /// Needs to be called before SDL_QuitSubSystem. void CloseJoysticks(); + // Set to true if SDL supports game controller subsystem + bool has_gamecontroller = false; + /// Map of GUID of a list of corresponding virtual Joysticks std::unordered_map<std::string, std::vector<std::shared_ptr<SDLJoystick>>> joystick_map; std::mutex joystick_map_mutex; diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp new file mode 100644 index 000000000..b66c05856 --- /dev/null +++ b/src/input_common/settings.cpp @@ -0,0 +1,40 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "input_common/settings.h" + +namespace Settings { +namespace NativeButton { +const std::array<const char*, NumButtons> mapping = {{ + "button_a", "button_b", "button_x", "button_y", "button_lstick", + "button_rstick", "button_l", "button_r", "button_zl", "button_zr", + "button_plus", "button_minus", "button_dleft", "button_dup", "button_dright", + "button_ddown", "button_sl", "button_sr", "button_home", "button_screenshot", +}}; +} + +namespace NativeMotion { +const std::array<const char*, NumMotions> mapping = {{ + "motionleft", + "motionright", +}}; +} + +namespace NativeAnalog { +const std::array<const char*, NumAnalogs> mapping = {{ + "lstick", + "rstick", +}}; +} + +namespace NativeMouseButton { +const std::array<const char*, NumMouseButtons> mapping = {{ + "left", + "right", + "middle", + "forward", + "back", +}}; +} +} // namespace Settings diff --git a/src/input_common/settings.h b/src/input_common/settings.h new file mode 100644 index 000000000..ab0b95cf1 --- /dev/null +++ b/src/input_common/settings.h @@ -0,0 +1,352 @@ +// Copyright 2020 yuzu Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <array> +#include <string> +#include "common/common_types.h" + +namespace Settings { +namespace NativeButton { +enum Values : int { + A, + B, + X, + Y, + LStick, + RStick, + L, + R, + ZL, + ZR, + Plus, + Minus, + + DLeft, + DUp, + DRight, + DDown, + + SL, + SR, + + Home, + Screenshot, + + NumButtons, +}; + +constexpr int BUTTON_HID_BEGIN = A; +constexpr int BUTTON_NS_BEGIN = Home; + +constexpr int BUTTON_HID_END = BUTTON_NS_BEGIN; +constexpr int BUTTON_NS_END = NumButtons; + +constexpr int NUM_BUTTONS_HID = BUTTON_HID_END - BUTTON_HID_BEGIN; +constexpr int NUM_BUTTONS_NS = BUTTON_NS_END - BUTTON_NS_BEGIN; + +extern const std::array<const char*, NumButtons> mapping; + +} // namespace NativeButton + +namespace NativeAnalog { +enum Values : int { + LStick, + RStick, + + NumAnalogs, +}; + +constexpr int STICK_HID_BEGIN = LStick; +constexpr int STICK_HID_END = NumAnalogs; +constexpr int NUM_STICKS_HID = NumAnalogs; + +extern const std::array<const char*, NumAnalogs> mapping; +} // namespace NativeAnalog + +namespace NativeMotion { +enum Values : int { + MOTIONLEFT, + MOTIONRIGHT, + + NumMotions, +}; + +constexpr int MOTION_HID_BEGIN = MOTIONLEFT; +constexpr int MOTION_HID_END = NumMotions; +constexpr int NUM_MOTION_HID = NumMotions; + +extern const std::array<const char*, NumMotions> mapping; +} // namespace NativeMotion + +namespace NativeMouseButton { +enum Values { + Left, + Right, + Middle, + Forward, + Back, + + NumMouseButtons, +}; + +constexpr int MOUSE_HID_BEGIN = Left; +constexpr int MOUSE_HID_END = NumMouseButtons; +constexpr int NUM_MOUSE_HID = NumMouseButtons; + +extern const std::array<const char*, NumMouseButtons> mapping; +} // namespace NativeMouseButton + +namespace NativeKeyboard { +enum Keys { + None, + Error, + + A = 4, + B, + C, + D, + E, + F, + G, + H, + I, + J, + K, + L, + M, + N, + O, + P, + Q, + R, + S, + T, + U, + V, + W, + X, + Y, + Z, + N1, + N2, + N3, + N4, + N5, + N6, + N7, + N8, + N9, + N0, + Enter, + Escape, + Backspace, + Tab, + Space, + Minus, + Equal, + LeftBrace, + RightBrace, + Backslash, + Tilde, + Semicolon, + Apostrophe, + Grave, + Comma, + Dot, + Slash, + CapsLockKey, + + F1, + F2, + F3, + F4, + F5, + F6, + F7, + F8, + F9, + F10, + F11, + F12, + + SystemRequest, + ScrollLockKey, + Pause, + Insert, + Home, + PageUp, + Delete, + End, + PageDown, + Right, + Left, + Down, + Up, + + NumLockKey, + KPSlash, + KPAsterisk, + KPMinus, + KPPlus, + KPEnter, + KP1, + KP2, + KP3, + KP4, + KP5, + KP6, + KP7, + KP8, + KP9, + KP0, + KPDot, + + Key102, + Compose, + Power, + KPEqual, + + F13, + F14, + F15, + F16, + F17, + F18, + F19, + F20, + F21, + F22, + F23, + F24, + + Open, + Help, + Properties, + Front, + Stop, + Repeat, + Undo, + Cut, + Copy, + Paste, + Find, + Mute, + VolumeUp, + VolumeDown, + CapsLockActive, + NumLockActive, + ScrollLockActive, + KPComma, + + KPLeftParenthesis, + KPRightParenthesis, + + LeftControlKey = 0xE0, + LeftShiftKey, + LeftAltKey, + LeftMetaKey, + RightControlKey, + RightShiftKey, + RightAltKey, + RightMetaKey, + + MediaPlayPause, + MediaStopCD, + MediaPrevious, + MediaNext, + MediaEject, + MediaVolumeUp, + MediaVolumeDown, + MediaMute, + MediaWebsite, + MediaBack, + MediaForward, + MediaStop, + MediaFind, + MediaScrollUp, + MediaScrollDown, + MediaEdit, + MediaSleep, + MediaCoffee, + MediaRefresh, + MediaCalculator, + + NumKeyboardKeys, +}; + +static_assert(NumKeyboardKeys == 0xFC, "Incorrect number of keyboard keys."); + +enum Modifiers { + LeftControl, + LeftShift, + LeftAlt, + LeftMeta, + RightControl, + RightShift, + RightAlt, + RightMeta, + CapsLock, + ScrollLock, + NumLock, + + NumKeyboardMods, +}; + +constexpr int KEYBOARD_KEYS_HID_BEGIN = None; +constexpr int KEYBOARD_KEYS_HID_END = NumKeyboardKeys; +constexpr int NUM_KEYBOARD_KEYS_HID = NumKeyboardKeys; + +constexpr int KEYBOARD_MODS_HID_BEGIN = LeftControl; +constexpr int KEYBOARD_MODS_HID_END = NumKeyboardMods; +constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; + +} // namespace NativeKeyboard + +using ButtonsRaw = std::array<std::string, NativeButton::NumButtons>; +using AnalogsRaw = std::array<std::string, NativeAnalog::NumAnalogs>; +using MotionRaw = std::array<std::string, NativeMotion::NumMotions>; +using MouseButtonsRaw = std::array<std::string, NativeMouseButton::NumMouseButtons>; +using KeyboardKeysRaw = std::array<std::string, NativeKeyboard::NumKeyboardKeys>; +using KeyboardModsRaw = std::array<std::string, NativeKeyboard::NumKeyboardMods>; + +constexpr u32 JOYCON_BODY_NEON_RED = 0xFF3C28; +constexpr u32 JOYCON_BUTTONS_NEON_RED = 0x1E0A0A; +constexpr u32 JOYCON_BODY_NEON_BLUE = 0x0AB9E6; +constexpr u32 JOYCON_BUTTONS_NEON_BLUE = 0x001E1E; + +enum class ControllerType { + ProController, + DualJoyconDetached, + LeftJoycon, + RightJoycon, + Handheld, +}; + +struct PlayerInput { + bool connected; + ControllerType controller_type; + ButtonsRaw buttons; + AnalogsRaw analogs; + MotionRaw motions; + std::string lstick_mod; + std::string rstick_mod; + + u32 body_color_left; + u32 body_color_right; + u32 button_color_left; + u32 button_color_right; +}; + +struct TouchscreenInput { + bool enabled; + std::string device; + + u32 finger; + u32 diameter_x; + u32 diameter_y; + u32 rotation_angle; +}; +} // namespace Settings diff --git a/src/input_common/touch_from_button.cpp b/src/input_common/touch_from_button.cpp new file mode 100644 index 000000000..98da0ef1a --- /dev/null +++ b/src/input_common/touch_from_button.cpp @@ -0,0 +1,50 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#include "core/frontend/framebuffer_layout.h" +#include "core/settings.h" +#include "input_common/touch_from_button.h" + +namespace InputCommon { + +class TouchFromButtonDevice final : public Input::TouchDevice { +public: + TouchFromButtonDevice() { + for (const auto& config_entry : + Settings::values.touch_from_button_maps[Settings::values.touch_from_button_map_index] + .buttons) { + const Common::ParamPackage package{config_entry}; + map.emplace_back( + Input::CreateDevice<Input::ButtonDevice>(config_entry), + std::clamp(package.Get("x", 0), 0, static_cast<int>(Layout::ScreenUndocked::Width)), + std::clamp(package.Get("y", 0), 0, + static_cast<int>(Layout::ScreenUndocked::Height))); + } + } + + std::tuple<float, float, bool> GetStatus() const override { + for (const auto& m : map) { + const bool state = std::get<0>(m)->GetStatus(); + if (state) { + const float x = static_cast<float>(std::get<1>(m)) / + static_cast<int>(Layout::ScreenUndocked::Width); + const float y = static_cast<float>(std::get<2>(m)) / + static_cast<int>(Layout::ScreenUndocked::Height); + return {x, y, true}; + } + } + return {}; + } + +private: + // A vector of the mapped button, its x and its y-coordinate + std::vector<std::tuple<std::unique_ptr<Input::ButtonDevice>, int, int>> map; +}; + +std::unique_ptr<Input::TouchDevice> TouchFromButtonFactory::Create( + const Common::ParamPackage& params) { + return std::make_unique<TouchFromButtonDevice>(); +} + +} // namespace InputCommon diff --git a/src/input_common/touch_from_button.h b/src/input_common/touch_from_button.h new file mode 100644 index 000000000..8b4d1aa96 --- /dev/null +++ b/src/input_common/touch_from_button.h @@ -0,0 +1,23 @@ +// Copyright 2020 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +#pragma once + +#include <memory> +#include "core/frontend/input.h" + +namespace InputCommon { + +/** + * A touch device factory that takes a list of button devices and combines them into a touch device. + */ +class TouchFromButtonFactory final : public Input::Factory<Input::TouchDevice> { +public: + /** + * Creates a touch device from a list of button devices + */ + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; +}; + +} // namespace InputCommon diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index da5227058..2b6a68d4b 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -2,15 +2,13 @@ // Licensed under GPLv2 or any later version // Refer to the license.txt file included. -#include <algorithm> -#include <array> #include <chrono> #include <cstring> #include <functional> #include <thread> #include <boost/asio.hpp> -#include <boost/bind.hpp> #include "common/logging/log.h" +#include "core/settings.h" #include "input_common/udp/client.h" #include "input_common/udp/protocol.h" @@ -132,21 +130,59 @@ static void SocketLoop(Socket* socket) { socket->Loop(); } -Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, - u8 pad_index, u32 client_id) - : status(std::move(status)) { - StartCommunication(host, port, pad_index, client_id); +Client::Client() { + LOG_INFO(Input, "Udp Initialization started"); + for (std::size_t client = 0; client < clients.size(); client++) { + u8 pad = client % 4; + StartCommunication(client, Settings::values.udp_input_address, + Settings::values.udp_input_port, pad, 24872); + // Set motion parameters + // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode + // Real HW values are unknown, 0.0001 is an approximate to Standard + clients[client].motion.SetGyroThreshold(0.0001f); + } } Client::~Client() { - socket->Stop(); - thread.join(); + Reset(); +} + +std::vector<Common::ParamPackage> Client::GetInputDevices() const { + std::vector<Common::ParamPackage> devices; + for (std::size_t client = 0; client < clients.size(); client++) { + if (!DeviceConnected(client)) { + continue; + } + std::string name = fmt::format("UDP Controller {}", client); + devices.emplace_back(Common::ParamPackage{ + {"class", "cemuhookudp"}, + {"display", std::move(name)}, + {"port", std::to_string(client)}, + }); + } + return devices; } +bool Client::DeviceConnected(std::size_t pad) const { + // Use last timestamp to detect if the socket has stopped sending data + const auto now = std::chrono::system_clock::now(); + u64 time_difference = + std::chrono::duration_cast<std::chrono::milliseconds>(now - clients[pad].last_motion_update) + .count(); + return time_difference < 1000 && clients[pad].active == 1; +} + +void Client::ReloadUDPClient() { + for (std::size_t client = 0; client < clients.size(); client++) { + ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, client); + } +} void Client::ReloadSocket(const std::string& host, u16 port, u8 pad_index, u32 client_id) { - socket->Stop(); - thread.join(); - StartCommunication(host, port, pad_index, client_id); + // client number must be determined from host / port and pad index + std::size_t client = pad_index; + clients[client].socket->Stop(); + clients[client].thread.join(); + StartCommunication(client, host, port, pad_index, client_id); } void Client::OnVersion(Response::Version data) { @@ -158,23 +194,39 @@ void Client::OnPortInfo(Response::PortInfo data) { } void Client::OnPadData(Response::PadData data) { + // client number must be determined from host / port and pad index + std::size_t client = data.info.id; LOG_TRACE(Input, "PadData packet received"); - if (data.packet_counter <= packet_sequence) { + if (data.packet_counter == clients[client].packet_sequence) { LOG_WARNING( Input, "PadData packet dropped because its stale info. Current count: {} Packet count: {}", - packet_sequence, data.packet_counter); + clients[client].packet_sequence, data.packet_counter); return; } - packet_sequence = data.packet_counter; - // TODO: Check how the Switch handles motions and how the CemuhookUDP motion - // directions correspond to the ones of the Switch - Common::Vec3f accel = Common::MakeVec<float>(data.accel.x, data.accel.y, data.accel.z); - Common::Vec3f gyro = Common::MakeVec<float>(data.gyro.pitch, data.gyro.yaw, data.gyro.roll); - { - std::lock_guard guard(status->update_mutex); + clients[client].active = data.info.is_pad_active; + clients[client].packet_sequence = data.packet_counter; + const auto now = std::chrono::system_clock::now(); + u64 time_difference = std::chrono::duration_cast<std::chrono::microseconds>( + now - clients[client].last_motion_update) + .count(); + clients[client].last_motion_update = now; + Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw}; + clients[client].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y}); + // Gyroscope values are not it the correct scale from better joy. + // Dividing by 312 allows us to make one full turn = 1 turn + // This must be a configurable valued called sensitivity + clients[client].motion.SetGyroscope(raw_gyroscope / 312.0f); + clients[client].motion.UpdateRotation(time_difference); + clients[client].motion.UpdateOrientation(time_difference); + Common::Vec3f gyroscope = clients[client].motion.GetGyroscope(); + Common::Vec3f accelerometer = clients[client].motion.GetAcceleration(); + Common::Vec3f rotation = clients[client].motion.GetRotations(); + std::array<Common::Vec3f, 3> orientation = clients[client].motion.GetOrientation(); - status->motion_status = {accel, gyro}; + { + std::lock_guard guard(clients[client].status.update_mutex); + clients[client].status.motion_status = {accelerometer, gyroscope, rotation, orientation}; // TODO: add a setting for "click" touch. Click touch refers to a device that differentiates // between a simple "tap" and a hard press that causes the touch screen to click. @@ -183,11 +235,11 @@ void Client::OnPadData(Response::PadData data) { float x = 0; float y = 0; - if (is_active && status->touch_calibration) { - const u16 min_x = status->touch_calibration->min_x; - const u16 max_x = status->touch_calibration->max_x; - const u16 min_y = status->touch_calibration->min_y; - const u16 max_y = status->touch_calibration->max_y; + if (is_active && clients[client].status.touch_calibration) { + const u16 min_x = clients[client].status.touch_calibration->min_x; + const u16 max_x = clients[client].status.touch_calibration->max_x; + const u16 min_y = clients[client].status.touch_calibration->min_y; + const u16 max_y = clients[client].status.touch_calibration->max_y; x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / static_cast<float>(max_x - min_x); @@ -195,17 +247,80 @@ void Client::OnPadData(Response::PadData data) { static_cast<float>(max_y - min_y); } - status->touch_status = {x, y, is_active}; + clients[client].status.touch_status = {x, y, is_active}; + + if (configuring) { + UpdateYuzuSettings(client, accelerometer, gyroscope, is_active); + } } } -void Client::StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id) { +void Client::StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index, + u32 client_id) { SocketCallback callback{[this](Response::Version version) { OnVersion(version); }, [this](Response::PortInfo info) { OnPortInfo(info); }, [this](Response::PadData data) { OnPadData(data); }}; LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port); - socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); - thread = std::thread{SocketLoop, this->socket.get()}; + clients[client].socket = std::make_unique<Socket>(host, port, pad_index, client_id, callback); + clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()}; +} + +void Client::Reset() { + for (std::size_t client = 0; client < clients.size(); client++) { + clients[client].socket->Stop(); + clients[client].thread.join(); + } +} + +void Client::UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc, + const Common::Vec3<float>& gyro, bool touch) { + UDPPadStatus pad; + if (touch) { + pad.touch = PadTouch::Click; + pad_queue[client].Push(pad); + } + for (size_t i = 0; i < 3; ++i) { + if (gyro[i] > 6.0f || gyro[i] < -6.0f) { + pad.motion = static_cast<PadMotion>(i); + pad.motion_value = gyro[i]; + pad_queue[client].Push(pad); + } + if (acc[i] > 2.0f || acc[i] < -2.0f) { + pad.motion = static_cast<PadMotion>(i + 3); + pad.motion_value = acc[i]; + pad_queue[client].Push(pad); + } + } +} + +void Client::BeginConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = true; +} + +void Client::EndConfiguration() { + for (auto& pq : pad_queue) { + pq.Clear(); + } + configuring = false; +} + +DeviceStatus& Client::GetPadState(std::size_t pad) { + return clients[pad].status; +} + +const DeviceStatus& Client::GetPadState(std::size_t pad) const { + return clients[pad].status; +} + +std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() { + return pad_queue; +} + +const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& Client::GetPadQueue() const { + return pad_queue; } void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, @@ -225,8 +340,7 @@ void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 clie } else { failure_callback(); } - }) - .detach(); + }).detach(); } CalibrationConfigurationJob::CalibrationConfigurationJob( @@ -234,7 +348,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( std::function<void(Status)> status_callback, std::function<void(u16, u16, u16, u16)> data_callback) { - std::thread([=] { + std::thread([=, this] { constexpr u16 CALIBRATION_THRESHOLD = 100; u16 min_x{UINT16_MAX}; @@ -280,8 +394,7 @@ CalibrationConfigurationJob::CalibrationConfigurationJob( complete_event.Wait(); socket.Stop(); worker_thread.join(); - }) - .detach(); + }).detach(); } CalibrationConfigurationJob::~CalibrationConfigurationJob() { diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h index b8c654755..523dc6a7a 100644 --- a/src/input_common/udp/client.h +++ b/src/input_common/udp/client.h @@ -12,8 +12,12 @@ #include <thread> #include <tuple> #include "common/common_types.h" +#include "common/param_package.h" #include "common/thread.h" +#include "common/threadsafe_queue.h" #include "common/vector_math.h" +#include "core/frontend/input.h" +#include "input_common/motion_input.h" namespace InputCommon::CemuhookUDP { @@ -28,9 +32,30 @@ struct PortInfo; struct Version; } // namespace Response +enum class PadMotion { + GyroX, + GyroY, + GyroZ, + AccX, + AccY, + AccZ, + Undefined, +}; + +enum class PadTouch { + Click, + Undefined, +}; + +struct UDPPadStatus { + PadTouch touch{PadTouch::Undefined}; + PadMotion motion{PadMotion::Undefined}; + f32 motion_value{0.0f}; +}; + struct DeviceStatus { std::mutex update_mutex; - std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; + Input::MotionStatus motion_status; std::tuple<float, float, bool> touch_status; // calibration data for scaling the device's touch area to 3ds @@ -45,22 +70,58 @@ struct DeviceStatus { class Client { public: - explicit Client(std::shared_ptr<DeviceStatus> status, const std::string& host = DEFAULT_ADDR, - u16 port = DEFAULT_PORT, u8 pad_index = 0, u32 client_id = 24872); + // Initialize the UDP client capture and read sequence + Client(); + + // Close and release the client ~Client(); + + // Used for polling + void BeginConfiguration(); + void EndConfiguration(); + + std::vector<Common::ParamPackage> GetInputDevices() const; + + bool DeviceConnected(std::size_t pad) const; + void ReloadUDPClient(); void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, u32 client_id = 24872); + std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue(); + const std::array<Common::SPSCQueue<UDPPadStatus>, 4>& GetPadQueue() const; + + DeviceStatus& GetPadState(std::size_t pad); + const DeviceStatus& GetPadState(std::size_t pad) const; + private: + struct ClientData { + std::unique_ptr<Socket> socket; + DeviceStatus status; + std::thread thread; + u64 packet_sequence = 0; + u8 active; + + // Realtime values + // motion is initalized with PID values for drift correction on joycons + InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f}; + std::chrono::time_point<std::chrono::system_clock> last_motion_update; + }; + + // For shutting down, clear all data, join all threads, release usb + void Reset(); + void OnVersion(Response::Version); void OnPortInfo(Response::PortInfo); void OnPadData(Response::PadData); - void StartCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id); + void StartCommunication(std::size_t client, const std::string& host, u16 port, u8 pad_index, + u32 client_id); + void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc, + const Common::Vec3<float>& gyro, bool touch); + + bool configuring = false; - std::unique_ptr<Socket> socket; - std::shared_ptr<DeviceStatus> status; - std::thread thread; - u64 packet_sequence = 0; + std::array<ClientData, 4> clients; + std::array<Common::SPSCQueue<UDPPadStatus>, 4> pad_queue; }; /// An async job allowing configuration of the touchpad calibration. diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp index 8c6ef1394..eba077a36 100644 --- a/src/input_common/udp/udp.cpp +++ b/src/input_common/udp/udp.cpp @@ -1,99 +1,144 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. +#include <atomic> +#include <list> #include <mutex> -#include <optional> -#include <tuple> - -#include "common/param_package.h" -#include "core/frontend/input.h" -#include "core/settings.h" +#include <utility> +#include "common/assert.h" +#include "common/threadsafe_queue.h" #include "input_common/udp/client.h" #include "input_common/udp/udp.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class UDPTouchDevice final : public Input::TouchDevice { +class UDPMotion final : public Input::MotionDevice { public: - explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} - std::tuple<float, float, bool> GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->touch_status; + UDPMotion(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) + : ip(ip_), port(port_), pad(pad_), client(client_) {} + + Input::MotionStatus GetStatus() const override { + return client->GetPadState(pad).motion_status; } private: - std::shared_ptr<DeviceStatus> status; + const std::string ip; + const int port; + const int pad; + CemuhookUDP::Client* client; + mutable std::mutex mutex; }; -class UDPMotionDevice final : public Input::MotionDevice { -public: - explicit UDPMotionDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} - std::tuple<Common::Vec3<float>, Common::Vec3<float>> GetStatus() const override { - std::lock_guard guard(status->update_mutex); - return status->motion_status; - } +/// A motion device factory that creates motion devices from JC Adapter +UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_) + : client(std::move(client_)) {} + +/** + * Creates motion device + * @param params contains parameters for creating the device: + * - "port": the nth jcpad on the adapter + */ +std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) { + const std::string ip = params.Get("ip", "127.0.0.1"); + const int port = params.Get("port", 26760); + const int pad = params.Get("pad_index", 0); + + return std::make_unique<UDPMotion>(ip, port, pad, client.get()); +} -private: - std::shared_ptr<DeviceStatus> status; -}; +void UDPMotionFactory::BeginConfiguration() { + polling = true; + client->BeginConfiguration(); +} -class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { -public: - explicit UDPTouchFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} - - std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override { - { - std::lock_guard guard(status->update_mutex); - status->touch_calibration = DeviceStatus::CalibrationData{}; - // These default values work well for DS4 but probably not other touch inputs - status->touch_calibration->min_x = params.Get("min_x", 100); - status->touch_calibration->min_y = params.Get("min_y", 50); - status->touch_calibration->max_x = params.Get("max_x", 1800); - status->touch_calibration->max_y = params.Get("max_y", 850); +void UDPMotionFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); +} + +Common::ParamPackage UDPMotionFactory::GetNextInput() { + Common::ParamPackage params; + CemuhookUDP::UDPPadStatus pad; + auto& queue = client->GetPadQueue(); + for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) { + while (queue[pad_number].Pop(pad)) { + if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) { + continue; + } + params.Set("engine", "cemuhookudp"); + params.Set("ip", "127.0.0.1"); + params.Set("port", 26760); + params.Set("pad_index", static_cast<int>(pad_number)); + params.Set("motion", static_cast<u16>(pad.motion)); + return params; } - return std::make_unique<UDPTouchDevice>(status); } + return params; +} -private: - std::shared_ptr<DeviceStatus> status; -}; - -class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { +class UDPTouch final : public Input::TouchDevice { public: - explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} + UDPTouch(std::string ip_, int port_, int pad_, CemuhookUDP::Client* client_) + : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {} - std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { - return std::make_unique<UDPMotionDevice>(status); + std::tuple<float, float, bool> GetStatus() const override { + return client->GetPadState(pad).touch_status; } private: - std::shared_ptr<DeviceStatus> status; + const std::string ip; + const int port; + const int pad; + CemuhookUDP::Client* client; + mutable std::mutex mutex; }; -State::State() { - auto status = std::make_shared<DeviceStatus>(); - client = - std::make_unique<Client>(status, Settings::values.udp_input_address, - Settings::values.udp_input_port, Settings::values.udp_pad_index); - - Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", - std::make_shared<UDPTouchFactory>(status)); - Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", - std::make_shared<UDPMotionFactory>(status)); +/// A motion device factory that creates motion devices from JC Adapter +UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_) + : client(std::move(client_)) {} + +/** + * Creates motion device + * @param params contains parameters for creating the device: + * - "port": the nth jcpad on the adapter + */ +std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) { + const std::string ip = params.Get("ip", "127.0.0.1"); + const int port = params.Get("port", 26760); + const int pad = params.Get("pad_index", 0); + + return std::make_unique<UDPTouch>(ip, port, pad, client.get()); } -State::~State() { - Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); - Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); +void UDPTouchFactory::BeginConfiguration() { + polling = true; + client->BeginConfiguration(); } -void State::ReloadUDPClient() { - client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, - Settings::values.udp_pad_index); +void UDPTouchFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); } -std::unique_ptr<State> Init() { - return std::make_unique<State>(); +Common::ParamPackage UDPTouchFactory::GetNextInput() { + Common::ParamPackage params; + CemuhookUDP::UDPPadStatus pad; + auto& queue = client->GetPadQueue(); + for (std::size_t pad_number = 0; pad_number < queue.size(); ++pad_number) { + while (queue[pad_number].Pop(pad)) { + if (pad.touch == CemuhookUDP::PadTouch::Undefined) { + continue; + } + params.Set("engine", "cemuhookudp"); + params.Set("ip", "127.0.0.1"); + params.Set("port", 26760); + params.Set("pad_index", static_cast<int>(pad_number)); + params.Set("touch", static_cast<u16>(pad.touch)); + return params; + } + } + return params; } -} // namespace InputCommon::CemuhookUDP + +} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h index 4f83f0441..ea3fd4175 100644 --- a/src/input_common/udp/udp.h +++ b/src/input_common/udp/udp.h @@ -1,25 +1,57 @@ -// Copyright 2018 Citra Emulator Project +// Copyright 2020 yuzu Emulator Project // Licensed under GPLv2 or any later version // Refer to the license.txt file included. #pragma once #include <memory> +#include "core/frontend/input.h" +#include "input_common/udp/client.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class Client; - -class State { +/// A motion device factory that creates motion devices from udp clients +class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { public: - State(); - ~State(); - void ReloadUDPClient(); + explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_); + + std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } private: - std::unique_ptr<Client> client; + std::shared_ptr<CemuhookUDP::Client> client; + bool polling = false; }; -std::unique_ptr<State> Init(); +/// A touch device factory that creates touch devices from udp clients +class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { +public: + explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_); + + std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override; + + Common::ParamPackage GetNextInput(); + + /// For device input configuration/polling + void BeginConfiguration(); + void EndConfiguration(); + + bool IsPolling() const { + return polling; + } + +private: + std::shared_ptr<CemuhookUDP::Client> client; + bool polling = false; +}; -} // namespace InputCommon::CemuhookUDP +} // namespace InputCommon |