Merge pull request #2800 from FearlessTobi/port-4049
Port citra-emu/citra#4049: "Input: UDP Client to provide motion and touch controls"
This commit is contained in:
		| @@ -350,6 +350,13 @@ function(create_target_directory_groups target_name) | |||||||
|     endforeach() |     endforeach() | ||||||
| endfunction() | endfunction() | ||||||
|  |  | ||||||
|  | # Prevent boost from linking against libs when building | ||||||
|  | add_definitions(-DBOOST_ERROR_CODE_HEADER_ONLY | ||||||
|  |     -DBOOST_SYSTEM_NO_LIB | ||||||
|  |     -DBOOST_DATE_TIME_NO_LIB | ||||||
|  |     -DBOOST_REGEX_NO_LIB | ||||||
|  | ) | ||||||
|  |  | ||||||
| enable_testing() | enable_testing() | ||||||
| add_subdirectory(externals) | add_subdirectory(externals) | ||||||
| add_subdirectory(src) | add_subdirectory(src) | ||||||
|   | |||||||
| @@ -28,6 +28,15 @@ public: | |||||||
|         is_set = false; |         is_set = false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|  |     template <class Duration> | ||||||
|  |     bool WaitFor(const std::chrono::duration<Duration>& time) { | ||||||
|  |         std::unique_lock lk{mutex}; | ||||||
|  |         if (!condvar.wait_for(lk, time, [this] { return is_set; })) | ||||||
|  |             return false; | ||||||
|  |         is_set = false; | ||||||
|  |         return true; | ||||||
|  |     } | ||||||
|  |  | ||||||
|     template <class Clock, class Duration> |     template <class Clock, class Duration> | ||||||
|     bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { |     bool WaitUntil(const std::chrono::time_point<Clock, Duration>& time) { | ||||||
|         std::unique_lock lk{mutex}; |         std::unique_lock lk{mutex}; | ||||||
|   | |||||||
| @@ -401,6 +401,9 @@ struct Values { | |||||||
|     std::string motion_device; |     std::string motion_device; | ||||||
|     TouchscreenInput touchscreen; |     TouchscreenInput touchscreen; | ||||||
|     std::atomic_bool is_device_reload_pending{true}; |     std::atomic_bool is_device_reload_pending{true}; | ||||||
|  |     std::string udp_input_address; | ||||||
|  |     u16 udp_input_port; | ||||||
|  |     u8 udp_pad_index; | ||||||
|  |  | ||||||
|     // Core |     // Core | ||||||
|     bool use_multi_core; |     bool use_multi_core; | ||||||
|   | |||||||
| @@ -9,6 +9,12 @@ add_library(input_common STATIC | |||||||
|     motion_emu.h |     motion_emu.h | ||||||
|     sdl/sdl.cpp |     sdl/sdl.cpp | ||||||
|     sdl/sdl.h |     sdl/sdl.h | ||||||
|  |     udp/client.cpp | ||||||
|  |     udp/client.h | ||||||
|  |     udp/protocol.cpp | ||||||
|  |     udp/protocol.h | ||||||
|  |     udp/udp.cpp | ||||||
|  |     udp/udp.h | ||||||
| ) | ) | ||||||
|  |  | ||||||
| if(SDL2_FOUND) | if(SDL2_FOUND) | ||||||
| @@ -21,4 +27,4 @@ if(SDL2_FOUND) | |||||||
| endif() | endif() | ||||||
|  |  | ||||||
| create_target_directory_groups(input_common) | create_target_directory_groups(input_common) | ||||||
| target_link_libraries(input_common PUBLIC core PRIVATE common) | target_link_libraries(input_common PUBLIC core PRIVATE common ${Boost_LIBRARIES}) | ||||||
|   | |||||||
| @@ -9,6 +9,7 @@ | |||||||
| #include "input_common/keyboard.h" | #include "input_common/keyboard.h" | ||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
| #include "input_common/motion_emu.h" | #include "input_common/motion_emu.h" | ||||||
|  | #include "input_common/udp/udp.h" | ||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
| #include "input_common/sdl/sdl.h" | #include "input_common/sdl/sdl.h" | ||||||
| #endif | #endif | ||||||
| @@ -18,6 +19,7 @@ namespace InputCommon { | |||||||
| static std::shared_ptr<Keyboard> keyboard; | static std::shared_ptr<Keyboard> keyboard; | ||||||
| static std::shared_ptr<MotionEmu> motion_emu; | static std::shared_ptr<MotionEmu> motion_emu; | ||||||
| static std::unique_ptr<SDL::State> sdl; | static std::unique_ptr<SDL::State> sdl; | ||||||
|  | static std::unique_ptr<CemuhookUDP::State> udp; | ||||||
|  |  | ||||||
| void Init() { | void Init() { | ||||||
|     keyboard = std::make_shared<Keyboard>(); |     keyboard = std::make_shared<Keyboard>(); | ||||||
| @@ -28,6 +30,8 @@ void Init() { | |||||||
|     Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); |     Input::RegisterFactory<Input::MotionDevice>("motion_emu", motion_emu); | ||||||
|  |  | ||||||
|     sdl = SDL::Init(); |     sdl = SDL::Init(); | ||||||
|  |  | ||||||
|  |     udp = CemuhookUDP::Init(); | ||||||
| } | } | ||||||
|  |  | ||||||
| void Shutdown() { | void Shutdown() { | ||||||
| @@ -72,11 +76,13 @@ std::string GenerateAnalogParamFromKeys(int key_up, int key_down, int key_left, | |||||||
| namespace Polling { | namespace Polling { | ||||||
|  |  | ||||||
| std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { | std::vector<std::unique_ptr<DevicePoller>> GetPollers(DeviceType type) { | ||||||
|  |     std::vector<std::unique_ptr<DevicePoller>> pollers; | ||||||
|  |  | ||||||
| #ifdef HAVE_SDL2 | #ifdef HAVE_SDL2 | ||||||
|     return sdl->GetPollers(type); |     pollers = sdl->GetPollers(type); | ||||||
| #else |  | ||||||
|     return {}; |  | ||||||
| #endif | #endif | ||||||
|  |  | ||||||
|  |     return pollers; | ||||||
| } | } | ||||||
|  |  | ||||||
| } // namespace Polling | } // namespace Polling | ||||||
|   | |||||||
							
								
								
									
										287
									
								
								src/input_common/udp/client.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										287
									
								
								src/input_common/udp/client.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,287 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // 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 "input_common/udp/client.h" | ||||||
|  | #include "input_common/udp/protocol.h" | ||||||
|  |  | ||||||
|  | using boost::asio::ip::address_v4; | ||||||
|  | using boost::asio::ip::udp; | ||||||
|  |  | ||||||
|  | namespace InputCommon::CemuhookUDP { | ||||||
|  |  | ||||||
|  | struct SocketCallback { | ||||||
|  |     std::function<void(Response::Version)> version; | ||||||
|  |     std::function<void(Response::PortInfo)> port_info; | ||||||
|  |     std::function<void(Response::PadData)> pad_data; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class Socket { | ||||||
|  | public: | ||||||
|  |     using clock = std::chrono::system_clock; | ||||||
|  |  | ||||||
|  |     explicit Socket(const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||||
|  |                     SocketCallback callback) | ||||||
|  |         : client_id(client_id), timer(io_service), | ||||||
|  |           send_endpoint(udp::endpoint(address_v4::from_string(host), port)), | ||||||
|  |           socket(io_service, udp::endpoint(udp::v4(), 0)), pad_index(pad_index), | ||||||
|  |           callback(std::move(callback)) {} | ||||||
|  |  | ||||||
|  |     void Stop() { | ||||||
|  |         io_service.stop(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void Loop() { | ||||||
|  |         io_service.run(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void StartSend(const clock::time_point& from) { | ||||||
|  |         timer.expires_at(from + std::chrono::seconds(3)); | ||||||
|  |         timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void StartReceive() { | ||||||
|  |         socket.async_receive_from( | ||||||
|  |             boost::asio::buffer(receive_buffer), receive_endpoint, | ||||||
|  |             [this](const boost::system::error_code& error, std::size_t bytes_transferred) { | ||||||
|  |                 HandleReceive(error, bytes_transferred); | ||||||
|  |             }); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     void HandleReceive(const boost::system::error_code& error, std::size_t bytes_transferred) { | ||||||
|  |         if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) { | ||||||
|  |             switch (*type) { | ||||||
|  |             case Type::Version: { | ||||||
|  |                 Response::Version version; | ||||||
|  |                 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version)); | ||||||
|  |                 callback.version(std::move(version)); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             case Type::PortInfo: { | ||||||
|  |                 Response::PortInfo port_info; | ||||||
|  |                 std::memcpy(&port_info, &receive_buffer[sizeof(Header)], | ||||||
|  |                             sizeof(Response::PortInfo)); | ||||||
|  |                 callback.port_info(std::move(port_info)); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             case Type::PadData: { | ||||||
|  |                 Response::PadData pad_data; | ||||||
|  |                 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData)); | ||||||
|  |                 callback.pad_data(std::move(pad_data)); | ||||||
|  |                 break; | ||||||
|  |             } | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |         StartReceive(); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     void HandleSend(const boost::system::error_code& error) { | ||||||
|  |         // Send a request for getting port info for the pad | ||||||
|  |         Request::PortInfo port_info{1, {pad_index, 0, 0, 0}}; | ||||||
|  |         const auto port_message = Request::Create(port_info, client_id); | ||||||
|  |         std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE); | ||||||
|  |         socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint); | ||||||
|  |  | ||||||
|  |         // Send a request for getting pad data for the pad | ||||||
|  |         Request::PadData pad_data{Request::PadData::Flags::Id, pad_index, EMPTY_MAC_ADDRESS}; | ||||||
|  |         const auto pad_message = Request::Create(pad_data, client_id); | ||||||
|  |         std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE); | ||||||
|  |         socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint); | ||||||
|  |         StartSend(timer.expiry()); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     SocketCallback callback; | ||||||
|  |     boost::asio::io_service io_service; | ||||||
|  |     boost::asio::basic_waitable_timer<clock> timer; | ||||||
|  |     udp::socket socket; | ||||||
|  |  | ||||||
|  |     u32 client_id{}; | ||||||
|  |     u8 pad_index{}; | ||||||
|  |  | ||||||
|  |     static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>); | ||||||
|  |     static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>); | ||||||
|  |     std::array<u8, PORT_INFO_SIZE> send_buffer1; | ||||||
|  |     std::array<u8, PAD_DATA_SIZE> send_buffer2; | ||||||
|  |     udp::endpoint send_endpoint; | ||||||
|  |  | ||||||
|  |     std::array<u8, MAX_PACKET_SIZE> receive_buffer; | ||||||
|  |     udp::endpoint receive_endpoint; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | static void SocketLoop(Socket* socket) { | ||||||
|  |     socket->StartReceive(); | ||||||
|  |     socket->StartSend(Socket::clock::now()); | ||||||
|  |     socket->Loop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Client::Client(std::shared_ptr<DeviceStatus> status, const std::string& host, u16 port, | ||||||
|  |                u8 pad_index, u32 client_id) | ||||||
|  |     : status(status) { | ||||||
|  |     StartCommunication(host, port, pad_index, client_id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Client::~Client() { | ||||||
|  |     socket->Stop(); | ||||||
|  |     thread.join(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Client::OnVersion(Response::Version data) { | ||||||
|  |     LOG_TRACE(Input, "Version packet received: {}", data.version); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Client::OnPortInfo(Response::PortInfo data) { | ||||||
|  |     LOG_TRACE(Input, "PortInfo packet received: {}", data.model); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Client::OnPadData(Response::PadData data) { | ||||||
|  |     LOG_TRACE(Input, "PadData packet received"); | ||||||
|  |     if (data.packet_counter <= packet_sequence) { | ||||||
|  |         LOG_WARNING( | ||||||
|  |             Input, | ||||||
|  |             "PadData packet dropped because its stale info. Current count: {} Packet count: {}", | ||||||
|  |             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); | ||||||
|  |  | ||||||
|  |         status->motion_status = {accel, gyro}; | ||||||
|  |  | ||||||
|  |         // 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. | ||||||
|  |         const bool is_active = data.touch_1.is_active != 0; | ||||||
|  |  | ||||||
|  |         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; | ||||||
|  |  | ||||||
|  |             x = (std::clamp(static_cast<u16>(data.touch_1.x), min_x, max_x) - min_x) / | ||||||
|  |                 static_cast<float>(max_x - min_x); | ||||||
|  |             y = (std::clamp(static_cast<u16>(data.touch_1.y), min_y, max_y) - min_y) / | ||||||
|  |                 static_cast<float>(max_y - min_y); | ||||||
|  |         } | ||||||
|  |  | ||||||
|  |         status->touch_status = {x, y, is_active}; | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void Client::StartCommunication(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()}; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||||
|  |                        std::function<void()> success_callback, | ||||||
|  |                        std::function<void()> failure_callback) { | ||||||
|  |     std::thread([=] { | ||||||
|  |         Common::Event success_event; | ||||||
|  |         SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, | ||||||
|  |                                 [&](Response::PadData data) { success_event.Set(); }}; | ||||||
|  |         Socket socket{host, port, pad_index, client_id, callback}; | ||||||
|  |         std::thread worker_thread{SocketLoop, &socket}; | ||||||
|  |         bool result = success_event.WaitFor(std::chrono::seconds(8)); | ||||||
|  |         socket.Stop(); | ||||||
|  |         worker_thread.join(); | ||||||
|  |         if (result) { | ||||||
|  |             success_callback(); | ||||||
|  |         } else { | ||||||
|  |             failure_callback(); | ||||||
|  |         } | ||||||
|  |     }) | ||||||
|  |         .detach(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CalibrationConfigurationJob::CalibrationConfigurationJob( | ||||||
|  |     const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||||
|  |     std::function<void(Status)> status_callback, | ||||||
|  |     std::function<void(u16, u16, u16, u16)> data_callback) { | ||||||
|  |  | ||||||
|  |     std::thread([=] { | ||||||
|  |         constexpr u16 CALIBRATION_THRESHOLD = 100; | ||||||
|  |  | ||||||
|  |         u16 min_x{UINT16_MAX}; | ||||||
|  |         u16 min_y{UINT16_MAX}; | ||||||
|  |         u16 max_x{}; | ||||||
|  |         u16 max_y{}; | ||||||
|  |  | ||||||
|  |         Status current_status{Status::Initialized}; | ||||||
|  |         SocketCallback callback{[](Response::Version version) {}, [](Response::PortInfo info) {}, | ||||||
|  |                                 [&](Response::PadData data) { | ||||||
|  |                                     if (current_status == Status::Initialized) { | ||||||
|  |                                         // Receiving data means the communication is ready now | ||||||
|  |                                         current_status = Status::Ready; | ||||||
|  |                                         status_callback(current_status); | ||||||
|  |                                     } | ||||||
|  |                                     if (!data.touch_1.is_active) { | ||||||
|  |                                         return; | ||||||
|  |                                     } | ||||||
|  |                                     LOG_DEBUG(Input, "Current touch: {} {}", data.touch_1.x, | ||||||
|  |                                               data.touch_1.y); | ||||||
|  |                                     min_x = std::min(min_x, static_cast<u16>(data.touch_1.x)); | ||||||
|  |                                     min_y = std::min(min_y, static_cast<u16>(data.touch_1.y)); | ||||||
|  |                                     if (current_status == Status::Ready) { | ||||||
|  |                                         // First touch - min data (min_x/min_y) | ||||||
|  |                                         current_status = Status::Stage1Completed; | ||||||
|  |                                         status_callback(current_status); | ||||||
|  |                                     } | ||||||
|  |                                     if (data.touch_1.x - min_x > CALIBRATION_THRESHOLD && | ||||||
|  |                                         data.touch_1.y - min_y > CALIBRATION_THRESHOLD) { | ||||||
|  |                                         // Set the current position as max value and finishes | ||||||
|  |                                         // configuration | ||||||
|  |                                         max_x = data.touch_1.x; | ||||||
|  |                                         max_y = data.touch_1.y; | ||||||
|  |                                         current_status = Status::Completed; | ||||||
|  |                                         data_callback(min_x, min_y, max_x, max_y); | ||||||
|  |                                         status_callback(current_status); | ||||||
|  |  | ||||||
|  |                                         complete_event.Set(); | ||||||
|  |                                     } | ||||||
|  |                                 }}; | ||||||
|  |         Socket socket{host, port, pad_index, client_id, callback}; | ||||||
|  |         std::thread worker_thread{SocketLoop, &socket}; | ||||||
|  |         complete_event.Wait(); | ||||||
|  |         socket.Stop(); | ||||||
|  |         worker_thread.join(); | ||||||
|  |     }) | ||||||
|  |         .detach(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | CalibrationConfigurationJob::~CalibrationConfigurationJob() { | ||||||
|  |     Stop(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void CalibrationConfigurationJob::Stop() { | ||||||
|  |     complete_event.Set(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | } // namespace InputCommon::CemuhookUDP | ||||||
							
								
								
									
										96
									
								
								src/input_common/udp/client.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/input_common/udp/client.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <functional> | ||||||
|  | #include <memory> | ||||||
|  | #include <mutex> | ||||||
|  | #include <optional> | ||||||
|  | #include <string> | ||||||
|  | #include <thread> | ||||||
|  | #include <tuple> | ||||||
|  | #include <vector> | ||||||
|  | #include "common/common_types.h" | ||||||
|  | #include "common/thread.h" | ||||||
|  | #include "common/vector_math.h" | ||||||
|  |  | ||||||
|  | namespace InputCommon::CemuhookUDP { | ||||||
|  |  | ||||||
|  | constexpr u16 DEFAULT_PORT = 26760; | ||||||
|  | constexpr char DEFAULT_ADDR[] = "127.0.0.1"; | ||||||
|  |  | ||||||
|  | class Socket; | ||||||
|  |  | ||||||
|  | namespace Response { | ||||||
|  | struct PadData; | ||||||
|  | struct PortInfo; | ||||||
|  | struct Version; | ||||||
|  | } // namespace Response | ||||||
|  |  | ||||||
|  | struct DeviceStatus { | ||||||
|  |     std::mutex update_mutex; | ||||||
|  |     std::tuple<Common::Vec3<float>, Common::Vec3<float>> motion_status; | ||||||
|  |     std::tuple<float, float, bool> touch_status; | ||||||
|  |  | ||||||
|  |     // calibration data for scaling the device's touch area to 3ds | ||||||
|  |     struct CalibrationData { | ||||||
|  |         u16 min_x{}; | ||||||
|  |         u16 min_y{}; | ||||||
|  |         u16 max_x{}; | ||||||
|  |         u16 max_y{}; | ||||||
|  |     }; | ||||||
|  |     std::optional<CalibrationData> touch_calibration; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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); | ||||||
|  |     ~Client(); | ||||||
|  |     void ReloadSocket(const std::string& host = "127.0.0.1", u16 port = 26760, u8 pad_index = 0, | ||||||
|  |                       u32 client_id = 24872); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     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); | ||||||
|  |  | ||||||
|  |     std::unique_ptr<Socket> socket; | ||||||
|  |     std::shared_ptr<DeviceStatus> status; | ||||||
|  |     std::thread thread; | ||||||
|  |     u64 packet_sequence = 0; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | /// An async job allowing configuration of the touchpad calibration. | ||||||
|  | class CalibrationConfigurationJob { | ||||||
|  | public: | ||||||
|  |     enum class Status { | ||||||
|  |         Initialized, | ||||||
|  |         Ready, | ||||||
|  |         Stage1Completed, | ||||||
|  |         Completed, | ||||||
|  |     }; | ||||||
|  |     /** | ||||||
|  |      * Constructs and starts the job with the specified parameter. | ||||||
|  |      * | ||||||
|  |      * @param status_callback Callback for job status updates | ||||||
|  |      * @param data_callback Called when calibration data is ready | ||||||
|  |      */ | ||||||
|  |     explicit CalibrationConfigurationJob(const std::string& host, u16 port, u8 pad_index, | ||||||
|  |                                          u32 client_id, std::function<void(Status)> status_callback, | ||||||
|  |                                          std::function<void(u16, u16, u16, u16)> data_callback); | ||||||
|  |     ~CalibrationConfigurationJob(); | ||||||
|  |     void Stop(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     Common::Event complete_event; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | void TestCommunication(const std::string& host, u16 port, u8 pad_index, u32 client_id, | ||||||
|  |                        std::function<void()> success_callback, | ||||||
|  |                        std::function<void()> failure_callback); | ||||||
|  |  | ||||||
|  | } // namespace InputCommon::CemuhookUDP | ||||||
							
								
								
									
										79
									
								
								src/input_common/udp/protocol.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										79
									
								
								src/input_common/udp/protocol.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,79 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <cstddef> | ||||||
|  | #include <cstring> | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "input_common/udp/protocol.h" | ||||||
|  |  | ||||||
|  | namespace InputCommon::CemuhookUDP { | ||||||
|  |  | ||||||
|  | static constexpr std::size_t GetSizeOfResponseType(Type t) { | ||||||
|  |     switch (t) { | ||||||
|  |     case Type::Version: | ||||||
|  |         return sizeof(Response::Version); | ||||||
|  |     case Type::PortInfo: | ||||||
|  |         return sizeof(Response::PortInfo); | ||||||
|  |     case Type::PadData: | ||||||
|  |         return sizeof(Response::PadData); | ||||||
|  |     } | ||||||
|  |     return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | namespace Response { | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Returns Type if the packet is valid, else none | ||||||
|  |  * | ||||||
|  |  * Note: Modifies the buffer to zero out the crc (since thats the easiest way to check without | ||||||
|  |  * copying the buffer) | ||||||
|  |  */ | ||||||
|  | std::optional<Type> Validate(u8* data, std::size_t size) { | ||||||
|  |     if (size < sizeof(Header)) { | ||||||
|  |         LOG_DEBUG(Input, "Invalid UDP packet received"); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |     Header header{}; | ||||||
|  |     std::memcpy(&header, data, sizeof(Header)); | ||||||
|  |     if (header.magic != SERVER_MAGIC) { | ||||||
|  |         LOG_ERROR(Input, "UDP Packet has an unexpected magic value"); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |     if (header.protocol_version != PROTOCOL_VERSION) { | ||||||
|  |         LOG_ERROR(Input, "UDP Packet protocol mismatch"); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |     if (header.type < Type::Version || header.type > Type::PadData) { | ||||||
|  |         LOG_ERROR(Input, "UDP Packet is an unknown type"); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     // Packet size must equal sizeof(Header) + sizeof(Data) | ||||||
|  |     // and also verify that the packet info mentions the correct size. Since the spec includes the | ||||||
|  |     // type of the packet as part of the data, we need to include it in size calculations here | ||||||
|  |     // ie: payload_length == sizeof(T) + sizeof(Type) | ||||||
|  |     const std::size_t data_len = GetSizeOfResponseType(header.type); | ||||||
|  |     if (header.payload_length != data_len + sizeof(Type) || size < data_len + sizeof(Header)) { | ||||||
|  |         LOG_ERROR( | ||||||
|  |             Input, | ||||||
|  |             "UDP Packet payload length doesn't match. Received: {} PayloadLength: {} Expected: {}", | ||||||
|  |             size, header.payload_length, data_len + sizeof(Type)); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     const u32 crc32 = header.crc; | ||||||
|  |     boost::crc_32_type result; | ||||||
|  |     // zero out the crc in the buffer and then run the crc against it | ||||||
|  |     std::memset(&data[offsetof(Header, crc)], 0, sizeof(u32_le)); | ||||||
|  |  | ||||||
|  |     result.process_bytes(data, data_len + sizeof(Header)); | ||||||
|  |     if (crc32 != result.checksum()) { | ||||||
|  |         LOG_ERROR(Input, "UDP Packet CRC check failed. Offset: {}", offsetof(Header, crc)); | ||||||
|  |         return std::nullopt; | ||||||
|  |     } | ||||||
|  |     return header.type; | ||||||
|  | } | ||||||
|  | } // namespace Response | ||||||
|  |  | ||||||
|  | } // namespace InputCommon::CemuhookUDP | ||||||
							
								
								
									
										256
									
								
								src/input_common/udp/protocol.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										256
									
								
								src/input_common/udp/protocol.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,256 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #pragma once | ||||||
|  |  | ||||||
|  | #include <array> | ||||||
|  | #include <optional> | ||||||
|  | #include <type_traits> | ||||||
|  | #include <vector> | ||||||
|  | #include <boost/crc.hpp> | ||||||
|  | #include "common/bit_field.h" | ||||||
|  | #include "common/swap.h" | ||||||
|  |  | ||||||
|  | namespace InputCommon::CemuhookUDP { | ||||||
|  |  | ||||||
|  | constexpr std::size_t MAX_PACKET_SIZE = 100; | ||||||
|  | constexpr u16 PROTOCOL_VERSION = 1001; | ||||||
|  | constexpr u32 CLIENT_MAGIC = 0x43555344; // DSUC (but flipped for LE) | ||||||
|  | constexpr u32 SERVER_MAGIC = 0x53555344; // DSUS (but flipped for LE) | ||||||
|  |  | ||||||
|  | enum class Type : u32 { | ||||||
|  |     Version = 0x00100000, | ||||||
|  |     PortInfo = 0x00100001, | ||||||
|  |     PadData = 0x00100002, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | struct Header { | ||||||
|  |     u32_le magic{}; | ||||||
|  |     u16_le protocol_version{}; | ||||||
|  |     u16_le payload_length{}; | ||||||
|  |     u32_le crc{}; | ||||||
|  |     u32_le id{}; | ||||||
|  |     ///> In the protocol, the type of the packet is not part of the header, but its convenient to | ||||||
|  |     ///> include in the header so the callee doesn't have to duplicate the type twice when building | ||||||
|  |     ///> the data | ||||||
|  |     Type type{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(Header) == 20, "UDP Message Header struct has wrong size"); | ||||||
|  | static_assert(std::is_trivially_copyable_v<Header>, "UDP Message Header is not trivially copyable"); | ||||||
|  |  | ||||||
|  | using MacAddress = std::array<u8, 6>; | ||||||
|  | constexpr MacAddress EMPTY_MAC_ADDRESS = {0, 0, 0, 0, 0, 0}; | ||||||
|  |  | ||||||
|  | #pragma pack(push, 1) | ||||||
|  | template <typename T> | ||||||
|  | struct Message { | ||||||
|  |     Header header{}; | ||||||
|  |     T data; | ||||||
|  | }; | ||||||
|  | #pragma pack(pop) | ||||||
|  |  | ||||||
|  | template <typename T> | ||||||
|  | constexpr Type GetMessageType(); | ||||||
|  |  | ||||||
|  | namespace Request { | ||||||
|  |  | ||||||
|  | struct Version {}; | ||||||
|  | /** | ||||||
|  |  * Requests the server to send information about what controllers are plugged into the ports | ||||||
|  |  * In citra's case, we only have one controller, so for simplicity's sake, we can just send a | ||||||
|  |  * request explicitly for the first controller port and leave it at that. In the future it would be | ||||||
|  |  * nice to make this configurable | ||||||
|  |  */ | ||||||
|  | constexpr u32 MAX_PORTS = 4; | ||||||
|  | struct PortInfo { | ||||||
|  |     u32_le pad_count{}; ///> Number of ports to request data for | ||||||
|  |     std::array<u8, MAX_PORTS> port; | ||||||
|  | }; | ||||||
|  | static_assert(std::is_trivially_copyable_v<PortInfo>, | ||||||
|  |               "UDP Request PortInfo is not trivially copyable"); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Request the latest pad information from the server. If the server hasn't received this message | ||||||
|  |  * from the client in a reasonable time frame, the server will stop sending updates. The default | ||||||
|  |  * timeout seems to be 5 seconds. | ||||||
|  |  */ | ||||||
|  | struct PadData { | ||||||
|  |     enum class Flags : u8 { | ||||||
|  |         AllPorts, | ||||||
|  |         Id, | ||||||
|  |         Mac, | ||||||
|  |     }; | ||||||
|  |     /// Determines which method will be used as a look up for the controller | ||||||
|  |     Flags flags{}; | ||||||
|  |     /// Index of the port of the controller to retrieve data about | ||||||
|  |     u8 port_id{}; | ||||||
|  |     /// Mac address of the controller to retrieve data about | ||||||
|  |     MacAddress mac; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(PadData) == 8, "UDP Request PadData struct has wrong size"); | ||||||
|  | static_assert(std::is_trivially_copyable_v<PadData>, | ||||||
|  |               "UDP Request PadData is not trivially copyable"); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Creates a message with the proper header data that can be sent to the server. | ||||||
|  |  * @param T data Request body to send | ||||||
|  |  * @param client_id ID of the udp client (usually not checked on the server) | ||||||
|  |  */ | ||||||
|  | template <typename T> | ||||||
|  | Message<T> Create(const T data, const u32 client_id = 0) { | ||||||
|  |     boost::crc_32_type crc; | ||||||
|  |     Header header{ | ||||||
|  |         CLIENT_MAGIC, PROTOCOL_VERSION, sizeof(T) + sizeof(Type), 0, client_id, GetMessageType<T>(), | ||||||
|  |     }; | ||||||
|  |     Message<T> message{header, data}; | ||||||
|  |     crc.process_bytes(&message, sizeof(Message<T>)); | ||||||
|  |     message.header.crc = crc.checksum(); | ||||||
|  |     return message; | ||||||
|  | } | ||||||
|  | } // namespace Request | ||||||
|  |  | ||||||
|  | namespace Response { | ||||||
|  |  | ||||||
|  | struct Version { | ||||||
|  |     u16_le version{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(Version) == 2, "UDP Response Version struct has wrong size"); | ||||||
|  | static_assert(std::is_trivially_copyable_v<Version>, | ||||||
|  |               "UDP Response Version is not trivially copyable"); | ||||||
|  |  | ||||||
|  | struct PortInfo { | ||||||
|  |     u8 id{}; | ||||||
|  |     u8 state{}; | ||||||
|  |     u8 model{}; | ||||||
|  |     u8 connection_type{}; | ||||||
|  |     MacAddress mac; | ||||||
|  |     u8 battery{}; | ||||||
|  |     u8 is_pad_active{}; | ||||||
|  | }; | ||||||
|  | static_assert(sizeof(PortInfo) == 12, "UDP Response PortInfo struct has wrong size"); | ||||||
|  | static_assert(std::is_trivially_copyable_v<PortInfo>, | ||||||
|  |               "UDP Response PortInfo is not trivially copyable"); | ||||||
|  |  | ||||||
|  | #pragma pack(push, 1) | ||||||
|  | struct PadData { | ||||||
|  |     PortInfo info{}; | ||||||
|  |     u32_le packet_counter{}; | ||||||
|  |  | ||||||
|  |     u16_le digital_button{}; | ||||||
|  |     // The following union isn't trivially copyable but we don't use this input anyway. | ||||||
|  |     // union DigitalButton { | ||||||
|  |     //     u16_le button; | ||||||
|  |     //     BitField<0, 1, u16> button_1;   // Share | ||||||
|  |     //     BitField<1, 1, u16> button_2;   // L3 | ||||||
|  |     //     BitField<2, 1, u16> button_3;   // R3 | ||||||
|  |     //     BitField<3, 1, u16> button_4;   // Options | ||||||
|  |     //     BitField<4, 1, u16> button_5;   // Up | ||||||
|  |     //     BitField<5, 1, u16> button_6;   // Right | ||||||
|  |     //     BitField<6, 1, u16> button_7;   // Down | ||||||
|  |     //     BitField<7, 1, u16> button_8;   // Left | ||||||
|  |     //     BitField<8, 1, u16> button_9;   // L2 | ||||||
|  |     //     BitField<9, 1, u16> button_10;  // R2 | ||||||
|  |     //     BitField<10, 1, u16> button_11; // L1 | ||||||
|  |     //     BitField<11, 1, u16> button_12; // R1 | ||||||
|  |     //     BitField<12, 1, u16> button_13; // Triangle | ||||||
|  |     //     BitField<13, 1, u16> button_14; // Circle | ||||||
|  |     //     BitField<14, 1, u16> button_15; // Cross | ||||||
|  |     //     BitField<15, 1, u16> button_16; // Square | ||||||
|  |     // } digital_button; | ||||||
|  |  | ||||||
|  |     u8 home; | ||||||
|  |     /// If the device supports a "click" on the touchpad, this will change to 1 when a click happens | ||||||
|  |     u8 touch_hard_press{}; | ||||||
|  |     u8 left_stick_x{}; | ||||||
|  |     u8 left_stick_y{}; | ||||||
|  |     u8 right_stick_x{}; | ||||||
|  |     u8 right_stick_y{}; | ||||||
|  |  | ||||||
|  |     struct AnalogButton { | ||||||
|  |         u8 button_8{}; | ||||||
|  |         u8 button_7{}; | ||||||
|  |         u8 button_6{}; | ||||||
|  |         u8 button_5{}; | ||||||
|  |         u8 button_12{}; | ||||||
|  |         u8 button_11{}; | ||||||
|  |         u8 button_10{}; | ||||||
|  |         u8 button_9{}; | ||||||
|  |         u8 button_16{}; | ||||||
|  |         u8 button_15{}; | ||||||
|  |         u8 button_14{}; | ||||||
|  |         u8 button_13{}; | ||||||
|  |     } analog_button; | ||||||
|  |  | ||||||
|  |     struct TouchPad { | ||||||
|  |         u8 is_active{}; | ||||||
|  |         u8 id{}; | ||||||
|  |         u16_le x{}; | ||||||
|  |         u16_le y{}; | ||||||
|  |     } touch_1, touch_2; | ||||||
|  |  | ||||||
|  |     u64_le motion_timestamp; | ||||||
|  |  | ||||||
|  |     struct Accelerometer { | ||||||
|  |         float x{}; | ||||||
|  |         float y{}; | ||||||
|  |         float z{}; | ||||||
|  |     } accel; | ||||||
|  |  | ||||||
|  |     struct Gyroscope { | ||||||
|  |         float pitch{}; | ||||||
|  |         float yaw{}; | ||||||
|  |         float roll{}; | ||||||
|  |     } gyro; | ||||||
|  | }; | ||||||
|  | #pragma pack(pop) | ||||||
|  |  | ||||||
|  | static_assert(sizeof(PadData) == 80, "UDP Response PadData struct has wrong size "); | ||||||
|  | static_assert(std::is_trivially_copyable_v<PadData>, | ||||||
|  |               "UDP Response PadData is not trivially copyable"); | ||||||
|  |  | ||||||
|  | static_assert(sizeof(Message<PadData>) == MAX_PACKET_SIZE, | ||||||
|  |               "UDP MAX_PACKET_SIZE is no longer larger than Message<PadData>"); | ||||||
|  |  | ||||||
|  | static_assert(sizeof(PadData::AnalogButton) == 12, | ||||||
|  |               "UDP Response AnalogButton struct has wrong size "); | ||||||
|  | static_assert(sizeof(PadData::TouchPad) == 6, "UDP Response TouchPad struct has wrong size "); | ||||||
|  | static_assert(sizeof(PadData::Accelerometer) == 12, | ||||||
|  |               "UDP Response Accelerometer struct has wrong size "); | ||||||
|  | static_assert(sizeof(PadData::Gyroscope) == 12, "UDP Response Gyroscope struct has wrong size "); | ||||||
|  |  | ||||||
|  | /** | ||||||
|  |  * Create a Response Message from the data | ||||||
|  |  * @param data array of bytes sent from the server | ||||||
|  |  * @return boost::none if it failed to parse or Type if it succeeded. The client can then safely | ||||||
|  |  * copy the data into the appropriate struct for that Type | ||||||
|  |  */ | ||||||
|  | std::optional<Type> Validate(u8* data, std::size_t size); | ||||||
|  |  | ||||||
|  | } // namespace Response | ||||||
|  |  | ||||||
|  | template <> | ||||||
|  | constexpr Type GetMessageType<Request::Version>() { | ||||||
|  |     return Type::Version; | ||||||
|  | } | ||||||
|  | template <> | ||||||
|  | constexpr Type GetMessageType<Request::PortInfo>() { | ||||||
|  |     return Type::PortInfo; | ||||||
|  | } | ||||||
|  | template <> | ||||||
|  | constexpr Type GetMessageType<Request::PadData>() { | ||||||
|  |     return Type::PadData; | ||||||
|  | } | ||||||
|  | template <> | ||||||
|  | constexpr Type GetMessageType<Response::Version>() { | ||||||
|  |     return Type::Version; | ||||||
|  | } | ||||||
|  | template <> | ||||||
|  | constexpr Type GetMessageType<Response::PortInfo>() { | ||||||
|  |     return Type::PortInfo; | ||||||
|  | } | ||||||
|  | template <> | ||||||
|  | constexpr Type GetMessageType<Response::PadData>() { | ||||||
|  |     return Type::PadData; | ||||||
|  | } | ||||||
|  | } // namespace InputCommon::CemuhookUDP | ||||||
							
								
								
									
										96
									
								
								src/input_common/udp/udp.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										96
									
								
								src/input_common/udp/udp.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,96 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include "common/logging/log.h" | ||||||
|  | #include "common/param_package.h" | ||||||
|  | #include "core/frontend/input.h" | ||||||
|  | #include "core/settings.h" | ||||||
|  | #include "input_common/udp/client.h" | ||||||
|  | #include "input_common/udp/udp.h" | ||||||
|  |  | ||||||
|  | namespace InputCommon::CemuhookUDP { | ||||||
|  |  | ||||||
|  | class UDPTouchDevice final : public Input::TouchDevice { | ||||||
|  | public: | ||||||
|  |     explicit UDPTouchDevice(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||||
|  |     std::tuple<float, float, bool> GetStatus() const { | ||||||
|  |         std::lock_guard guard(status->update_mutex); | ||||||
|  |         return status->touch_status; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<DeviceStatus> status; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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 { | ||||||
|  |         std::lock_guard guard(status->update_mutex); | ||||||
|  |         return status->motion_status; | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<DeviceStatus> status; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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.emplace(); | ||||||
|  |             // 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); | ||||||
|  |         } | ||||||
|  |         return std::make_unique<UDPTouchDevice>(status); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<DeviceStatus> status; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||||||
|  | public: | ||||||
|  |     explicit UDPMotionFactory(std::shared_ptr<DeviceStatus> status_) : status(std::move(status_)) {} | ||||||
|  |  | ||||||
|  |     std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override { | ||||||
|  |         return std::make_unique<UDPMotionDevice>(status); | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::shared_ptr<DeviceStatus> status; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | 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)); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | State::~State() { | ||||||
|  |     Input::UnregisterFactory<Input::TouchDevice>("cemuhookudp"); | ||||||
|  |     Input::UnregisterFactory<Input::MotionDevice>("cemuhookudp"); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void State::ReloadUDPClient() { | ||||||
|  |     client->ReloadSocket(Settings::values.udp_input_address, Settings::values.udp_input_port, | ||||||
|  |                          Settings::values.udp_pad_index); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | std::unique_ptr<State> Init() { | ||||||
|  |     return std::make_unique<State>(); | ||||||
|  | } | ||||||
|  | } // namespace InputCommon::CemuhookUDP | ||||||
							
								
								
									
										27
									
								
								src/input_common/udp/udp.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								src/input_common/udp/udp.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | // Copyright 2018 Citra Emulator Project | ||||||
|  | // Licensed under GPLv2 or any later version | ||||||
|  | // Refer to the license.txt file included. | ||||||
|  |  | ||||||
|  | #include <memory> | ||||||
|  | #include <unordered_map> | ||||||
|  | #include "input_common/main.h" | ||||||
|  | #include "input_common/udp/client.h" | ||||||
|  |  | ||||||
|  | namespace InputCommon::CemuhookUDP { | ||||||
|  |  | ||||||
|  | class UDPTouchDevice; | ||||||
|  | class UDPMotionDevice; | ||||||
|  |  | ||||||
|  | class State { | ||||||
|  | public: | ||||||
|  |     State(); | ||||||
|  |     ~State(); | ||||||
|  |     void ReloadUDPClient(); | ||||||
|  |  | ||||||
|  | private: | ||||||
|  |     std::unique_ptr<Client> client; | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | std::unique_ptr<State> Init(); | ||||||
|  |  | ||||||
|  | } // namespace InputCommon::CemuhookUDP | ||||||
| @@ -10,6 +10,7 @@ | |||||||
| #include "core/hle/service/acc/profile_manager.h" | #include "core/hle/service/acc/profile_manager.h" | ||||||
| #include "core/hle/service/hid/controllers/npad.h" | #include "core/hle/service/hid/controllers/npad.h" | ||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
|  | #include "input_common/udp/client.h" | ||||||
| #include "yuzu/configuration/config.h" | #include "yuzu/configuration/config.h" | ||||||
| #include "yuzu/uisettings.h" | #include "yuzu/uisettings.h" | ||||||
|  |  | ||||||
| @@ -429,6 +430,16 @@ void Config::ReadControlValues() { | |||||||
|                     QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) |                     QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")) | ||||||
|             .toString() |             .toString() | ||||||
|             .toStdString(); |             .toStdString(); | ||||||
|  |     Settings::values.udp_input_address = | ||||||
|  |         ReadSetting(QStringLiteral("udp_input_address"), | ||||||
|  |                     QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)) | ||||||
|  |             .toString() | ||||||
|  |             .toStdString(); | ||||||
|  |     Settings::values.udp_input_port = static_cast<u16>( | ||||||
|  |         ReadSetting(QStringLiteral("udp_input_port"), InputCommon::CemuhookUDP::DEFAULT_PORT) | ||||||
|  |             .toInt()); | ||||||
|  |     Settings::values.udp_pad_index = | ||||||
|  |         static_cast<u8>(ReadSetting(QStringLiteral("udp_pad_index"), 0).toUInt()); | ||||||
|  |  | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| } | } | ||||||
| @@ -911,6 +922,12 @@ void Config::SaveControlValues() { | |||||||
|                  QString::fromStdString(Settings::values.motion_device), |                  QString::fromStdString(Settings::values.motion_device), | ||||||
|                  QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); |                  QStringLiteral("engine:motion_emu,update_period:100,sensitivity:0.01")); | ||||||
|     WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); |     WriteSetting(QStringLiteral("keyboard_enabled"), Settings::values.keyboard_enabled, false); | ||||||
|  |     WriteSetting(QStringLiteral("udp_input_address"), | ||||||
|  |                  QString::fromStdString(Settings::values.udp_input_address), | ||||||
|  |                  QString::fromUtf8(InputCommon::CemuhookUDP::DEFAULT_ADDR)); | ||||||
|  |     WriteSetting(QStringLiteral("udp_input_port"), Settings::values.udp_input_port, | ||||||
|  |                  InputCommon::CemuhookUDP::DEFAULT_PORT); | ||||||
|  |     WriteSetting(QStringLiteral("udp_pad_index"), Settings::values.udp_pad_index, 0); | ||||||
|  |  | ||||||
|     qt_config->endGroup(); |     qt_config->endGroup(); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -12,6 +12,7 @@ | |||||||
| #include "core/hle/service/acc/profile_manager.h" | #include "core/hle/service/acc/profile_manager.h" | ||||||
| #include "core/settings.h" | #include "core/settings.h" | ||||||
| #include "input_common/main.h" | #include "input_common/main.h" | ||||||
|  | #include "input_common/udp/client.h" | ||||||
| #include "yuzu_cmd/config.h" | #include "yuzu_cmd/config.h" | ||||||
| #include "yuzu_cmd/default_ini.h" | #include "yuzu_cmd/default_ini.h" | ||||||
|  |  | ||||||
| @@ -297,6 +298,10 @@ void Config::ReadValues() { | |||||||
|         sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); |         sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); | ||||||
|     Settings::values.touchscreen.diameter_y = |     Settings::values.touchscreen.diameter_y = | ||||||
|         sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); |         sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); | ||||||
|  |     Settings::values.udp_input_address = | ||||||
|  |         sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_ADDR); | ||||||
|  |     Settings::values.udp_input_port = static_cast<u16>(sdl2_config->GetInteger( | ||||||
|  |         "Controls", "udp_input_port", InputCommon::CemuhookUDP::DEFAULT_PORT)); | ||||||
|  |  | ||||||
|     std::transform(keyboard_keys.begin(), keyboard_keys.end(), |     std::transform(keyboard_keys.begin(), keyboard_keys.end(), | ||||||
|                    Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); |                    Settings::values.keyboard_keys.begin(), InputCommon::GenerateKeyboardParam); | ||||||
|   | |||||||
| @@ -69,12 +69,29 @@ rstick= | |||||||
| #  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: | #  - "motion_emu" (default) for emulating motion input from mouse input. Required parameters: | ||||||
| #      - "update_period": update period in milliseconds (default to 100) | #      - "update_period": update period in milliseconds (default to 100) | ||||||
| #      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) | #      - "sensitivity": the coefficient converting mouse movement to tilting angle (default to 0.01) | ||||||
|  | #  - "cemuhookudp" reads motion input from a udp server that uses cemuhook's udp protocol | ||||||
| motion_device= | motion_device= | ||||||
|  |  | ||||||
| # for touch input, the following devices are available: | # for touch input, the following devices are available: | ||||||
| #  - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required | #  - "emu_window" (default) for emulating touch input from mouse input to the emulation window. No parameters required | ||||||
|  | #  - "cemuhookudp" reads touch input from a udp server that uses cemuhook's udp protocol | ||||||
|  | #      - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system | ||||||
| touch_device= | touch_device= | ||||||
|  |  | ||||||
|  | # Most desktop operating systems do not expose a way to poll the motion state of the controllers | ||||||
|  | # so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly | ||||||
|  | # from a controller device to the client program. Citra has a client that can connect and read | ||||||
|  | # from any cemuhook compatible motion program. | ||||||
|  |  | ||||||
|  | # IPv4 address of the udp input server (Default "127.0.0.1") | ||||||
|  | udp_input_address= | ||||||
|  |  | ||||||
|  | # Port of the udp input server. (Default 26760) | ||||||
|  | udp_input_port= | ||||||
|  |  | ||||||
|  | # The pad to request data on. Should be between 0 (Pad 1) and 3 (Pad 4). (Default 0) | ||||||
|  | udp_pad_index= | ||||||
|  |  | ||||||
| [Core] | [Core] | ||||||
| # Whether to use multi-core for CPU emulation | # Whether to use multi-core for CPU emulation | ||||||
| # 0 (default): Disabled, 1: Enabled | # 0 (default): Disabled, 1: Enabled | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user