diff options
Diffstat (limited to 'src/input_common')
-rw-r--r-- | src/input_common/gcadapter/gc_adapter.cpp | 97 | ||||
-rw-r--r-- | src/input_common/gcadapter/gc_adapter.h | 5 | ||||
-rw-r--r-- | src/input_common/main.cpp | 57 | ||||
-rw-r--r-- | src/input_common/main.h | 24 | ||||
-rw-r--r-- | src/input_common/motion_emu.cpp | 17 | ||||
-rw-r--r-- | src/input_common/settings.cpp | 7 | ||||
-rw-r--r-- | src/input_common/settings.h | 17 | ||||
-rw-r--r-- | src/input_common/udp/client.cpp | 176 | ||||
-rw-r--r-- | src/input_common/udp/client.h | 77 | ||||
-rw-r--r-- | src/input_common/udp/udp.cpp | 181 | ||||
-rw-r--r-- | src/input_common/udp/udp.h | 61 |
11 files changed, 583 insertions, 136 deletions
diff --git a/src/input_common/gcadapter/gc_adapter.cpp b/src/input_common/gcadapter/gc_adapter.cpp index c6c423c4b..89c148aba 100644 --- a/src/input_common/gcadapter/gc_adapter.cpp +++ b/src/input_common/gcadapter/gc_adapter.cpp @@ -4,9 +4,20 @@ #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 { @@ -283,6 +294,92 @@ void Adapter::Reset() { } } +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; } diff --git a/src/input_common/gcadapter/gc_adapter.h b/src/input_common/gcadapter/gc_adapter.h index 20e97d283..75bf9fe74 100644 --- a/src/input_common/gcadapter/gc_adapter.h +++ b/src/input_common/gcadapter/gc_adapter.h @@ -10,6 +10,7 @@ #include <unordered_map> #include "common/common_types.h" #include "common/threadsafe_queue.h" +#include "input_common/main.h" struct libusb_context; struct libusb_device; @@ -75,6 +76,10 @@ public: 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; diff --git a/src/input_common/main.cpp b/src/input_common/main.cpp index ea1a1cee6..8da829132 100644 --- a/src/input_common/main.cpp +++ b/src/input_common/main.cpp @@ -12,6 +12,7 @@ #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" @@ -21,7 +22,7 @@ namespace InputCommon { struct InputSubsystem::Impl { void Initialize() { - auto gcadapter = std::make_shared<GCAdapter::Adapter>(); + gcadapter = std::make_shared<GCAdapter::Adapter>(); gcbuttons = std::make_shared<GCButtonFactory>(gcadapter); Input::RegisterFactory<Input::ButtonDevice>("gcpad", gcbuttons); gcanalog = std::make_shared<GCAnalogFactory>(gcadapter); @@ -40,7 +41,11 @@ struct InputSubsystem::Impl { sdl = SDL::Init(); #endif - udp = CemuhookUDP::Init(); + 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() { @@ -53,12 +58,17 @@ struct InputSubsystem::Impl { #ifdef HAVE_SDL2 sdl.reset(); #endif - udp.reset(); Input::UnregisterFactory<Input::ButtonDevice>("gcpad"); Input::UnregisterFactory<Input::AnalogDevice>("gcpad"); 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 { @@ -72,6 +82,8 @@ struct InputSubsystem::Impl { #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; } @@ -84,6 +96,9 @@ struct InputSubsystem::Impl { // 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); @@ -101,6 +116,9 @@ struct InputSubsystem::Impl { // 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); @@ -109,14 +127,29 @@ struct InputSubsystem::Impl { 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 {}; + } + std::shared_ptr<Keyboard> keyboard; std::shared_ptr<MotionEmu> motion_emu; #ifdef HAVE_SDL2 std::unique_ptr<SDL::State> sdl; #endif - std::unique_ptr<CemuhookUDP::State> udp; 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>()} {} @@ -175,6 +208,22 @@ 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; diff --git a/src/input_common/main.h b/src/input_common/main.h index f3fbf696e..dded3f1ef 100644 --- a/src/input_common/main.h +++ b/src/input_common/main.h @@ -21,10 +21,14 @@ namespace Settings::NativeButton { enum Values : int; } +namespace Settings::NativeMotion { +enum Values : int; +} + namespace InputCommon { namespace Polling { -enum class DeviceType { Button, AnalogPreferred }; +enum class DeviceType { Button, AnalogPreferred, Motion }; /** * A class that can be used to get inputs from an input device like controllers without having to @@ -50,6 +54,8 @@ public: class GCAnalogFactory; class GCButtonFactory; +class UDPMotionFactory; +class UDPTouchFactory; class Keyboard; class MotionEmu; @@ -59,6 +65,7 @@ class MotionEmu; */ 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: @@ -103,6 +110,9 @@ public: /// 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(); @@ -115,6 +125,18 @@ public: /// 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(); diff --git a/src/input_common/motion_emu.cpp b/src/input_common/motion_emu.cpp index d4cdf76a3..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(); } diff --git a/src/input_common/settings.cpp b/src/input_common/settings.cpp index 80c719cf4..b66c05856 100644 --- a/src/input_common/settings.cpp +++ b/src/input_common/settings.cpp @@ -14,6 +14,13 @@ const std::array<const char*, NumButtons> mapping = {{ }}; } +namespace NativeMotion { +const std::array<const char*, NumMotions> mapping = {{ + "motionleft", + "motionright", +}}; +} + namespace NativeAnalog { const std::array<const char*, NumAnalogs> mapping = {{ "lstick", diff --git a/src/input_common/settings.h b/src/input_common/settings.h index 2d258960b..ab0b95cf1 100644 --- a/src/input_common/settings.h +++ b/src/input_common/settings.h @@ -66,6 +66,21 @@ 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, @@ -292,6 +307,7 @@ constexpr int NUM_KEYBOARD_MODS_HID = NumKeyboardMods; 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>; @@ -314,6 +330,7 @@ struct PlayerInput { ControllerType controller_type; ButtonsRaw buttons; AnalogsRaw analogs; + MotionRaw motions; std::string lstick_mod; std::string rstick_mod; diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp index 3f4eaf448..2b6a68d4b 100644 --- a/src/input_common/udp/client.cpp +++ b/src/input_common/udp/client.cpp @@ -2,14 +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 "common/logging/log.h" +#include "core/settings.h" #include "input_common/udp/client.h" #include "input_common/udp/protocol.h" @@ -131,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) { @@ -157,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. @@ -182,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); @@ -194,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, 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 4b347e47e..eba077a36 100644 --- a/src/input_common/udp/udp.cpp +++ b/src/input_common/udp/udp.cpp @@ -1,105 +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); - - motion_factory = std::make_shared<UDPMotionFactory>(status); - touch_factory = std::make_shared<UDPTouchFactory>(status); - - Input::RegisterFactory<Input::MotionDevice>("cemuhookudp", motion_factory); - Input::RegisterFactory<Input::TouchDevice>("cemuhookudp", touch_factory); +/// 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(); } -std::vector<Common::ParamPackage> State::GetInputDevices() const { - // TODO support binding udp devices - return {}; +void UDPTouchFactory::EndConfiguration() { + polling = false; + client->EndConfiguration(); } -void State::ReloadUDPClient() { - client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, - Settings::values.udp_pad_index); +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; } -std::unique_ptr<State> Init() { - return std::make_unique<State>(); -} -} // namespace InputCommon::CemuhookUDP +} // namespace InputCommon diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h index 672a5c812..ea3fd4175 100644 --- a/src/input_common/udp/udp.h +++ b/src/input_common/udp/udp.h @@ -1,32 +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 <vector> -#include "common/param_package.h" +#include "core/frontend/input.h" +#include "input_common/udp/client.h" -namespace InputCommon::CemuhookUDP { +namespace InputCommon { -class Client; -class UDPMotionFactory; -class UDPTouchFactory; - -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(); - std::vector<Common::ParamPackage> GetInputDevices() const; + 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<UDPMotionFactory> motion_factory; - std::shared_ptr<UDPTouchFactory> touch_factory; + 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 |