summaryrefslogtreecommitdiff
path: root/src/input_common/udp
diff options
context:
space:
mode:
Diffstat (limited to 'src/input_common/udp')
-rw-r--r--src/input_common/udp/client.cpp526
-rw-r--r--src/input_common/udp/client.h183
-rw-r--r--src/input_common/udp/udp.cpp110
-rw-r--r--src/input_common/udp/udp.h57
4 files changed, 0 insertions, 876 deletions
diff --git a/src/input_common/udp/client.cpp b/src/input_common/udp/client.cpp
deleted file mode 100644
index bcc29c4e0..000000000
--- a/src/input_common/udp/client.cpp
+++ /dev/null
@@ -1,526 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <chrono>
6#include <cstring>
7#include <functional>
8#include <random>
9#include <thread>
10#include <boost/asio.hpp>
11#include "common/logging/log.h"
12#include "common/settings.h"
13#include "input_common/udp/client.h"
14#include "input_common/helpers/udp_protocol.h"
15
16using boost::asio::ip::udp;
17
18namespace InputCommon::CemuhookUDP {
19
20struct SocketCallback {
21 std::function<void(Response::Version)> version;
22 std::function<void(Response::PortInfo)> port_info;
23 std::function<void(Response::PadData)> pad_data;
24};
25
26class Socket {
27public:
28 using clock = std::chrono::system_clock;
29
30 explicit Socket(const std::string& host, u16 port, SocketCallback callback_)
31 : callback(std::move(callback_)), timer(io_service),
32 socket(io_service, udp::endpoint(udp::v4(), 0)), client_id(GenerateRandomClientId()) {
33 boost::system::error_code ec{};
34 auto ipv4 = boost::asio::ip::make_address_v4(host, ec);
35 if (ec.value() != boost::system::errc::success) {
36 LOG_ERROR(Input, "Invalid IPv4 address \"{}\" provided to socket", host);
37 ipv4 = boost::asio::ip::address_v4{};
38 }
39
40 send_endpoint = {udp::endpoint(ipv4, port)};
41 }
42
43 void Stop() {
44 io_service.stop();
45 }
46
47 void Loop() {
48 io_service.run();
49 }
50
51 void StartSend(const clock::time_point& from) {
52 timer.expires_at(from + std::chrono::seconds(3));
53 timer.async_wait([this](const boost::system::error_code& error) { HandleSend(error); });
54 }
55
56 void StartReceive() {
57 socket.async_receive_from(
58 boost::asio::buffer(receive_buffer), receive_endpoint,
59 [this](const boost::system::error_code& error, std::size_t bytes_transferred) {
60 HandleReceive(error, bytes_transferred);
61 });
62 }
63
64private:
65 u32 GenerateRandomClientId() const {
66 std::random_device device;
67 return device();
68 }
69
70 void HandleReceive(const boost::system::error_code&, std::size_t bytes_transferred) {
71 if (auto type = Response::Validate(receive_buffer.data(), bytes_transferred)) {
72 switch (*type) {
73 case Type::Version: {
74 Response::Version version;
75 std::memcpy(&version, &receive_buffer[sizeof(Header)], sizeof(Response::Version));
76 callback.version(std::move(version));
77 break;
78 }
79 case Type::PortInfo: {
80 Response::PortInfo port_info;
81 std::memcpy(&port_info, &receive_buffer[sizeof(Header)],
82 sizeof(Response::PortInfo));
83 callback.port_info(std::move(port_info));
84 break;
85 }
86 case Type::PadData: {
87 Response::PadData pad_data;
88 std::memcpy(&pad_data, &receive_buffer[sizeof(Header)], sizeof(Response::PadData));
89 SanitizeMotion(pad_data);
90 callback.pad_data(std::move(pad_data));
91 break;
92 }
93 }
94 }
95 StartReceive();
96 }
97
98 void HandleSend(const boost::system::error_code&) {
99 boost::system::error_code _ignored{};
100 // Send a request for getting port info for the pad
101 const Request::PortInfo port_info{4, {0, 1, 2, 3}};
102 const auto port_message = Request::Create(port_info, client_id);
103 std::memcpy(&send_buffer1, &port_message, PORT_INFO_SIZE);
104 socket.send_to(boost::asio::buffer(send_buffer1), send_endpoint, {}, _ignored);
105
106 // Send a request for getting pad data for the pad
107 const Request::PadData pad_data{
108 Request::PadData::Flags::AllPorts,
109 0,
110 EMPTY_MAC_ADDRESS,
111 };
112 const auto pad_message = Request::Create(pad_data, client_id);
113 std::memcpy(send_buffer2.data(), &pad_message, PAD_DATA_SIZE);
114 socket.send_to(boost::asio::buffer(send_buffer2), send_endpoint, {}, _ignored);
115 StartSend(timer.expiry());
116 }
117
118 void SanitizeMotion(Response::PadData& data) {
119 // Zero out any non number value
120 if (!std::isnormal(data.gyro.pitch)) {
121 data.gyro.pitch = 0;
122 }
123 if (!std::isnormal(data.gyro.roll)) {
124 data.gyro.roll = 0;
125 }
126 if (!std::isnormal(data.gyro.yaw)) {
127 data.gyro.yaw = 0;
128 }
129 if (!std::isnormal(data.accel.x)) {
130 data.accel.x = 0;
131 }
132 if (!std::isnormal(data.accel.y)) {
133 data.accel.y = 0;
134 }
135 if (!std::isnormal(data.accel.z)) {
136 data.accel.z = 0;
137 }
138 }
139
140 SocketCallback callback;
141 boost::asio::io_service io_service;
142 boost::asio::basic_waitable_timer<clock> timer;
143 udp::socket socket;
144
145 const u32 client_id;
146
147 static constexpr std::size_t PORT_INFO_SIZE = sizeof(Message<Request::PortInfo>);
148 static constexpr std::size_t PAD_DATA_SIZE = sizeof(Message<Request::PadData>);
149 std::array<u8, PORT_INFO_SIZE> send_buffer1;
150 std::array<u8, PAD_DATA_SIZE> send_buffer2;
151 udp::endpoint send_endpoint;
152
153 std::array<u8, MAX_PACKET_SIZE> receive_buffer;
154 udp::endpoint receive_endpoint;
155};
156
157static void SocketLoop(Socket* socket) {
158 socket->StartReceive();
159 socket->StartSend(Socket::clock::now());
160 socket->Loop();
161}
162
163Client::Client() {
164 LOG_INFO(Input, "Udp Initialization started");
165 finger_id.fill(MAX_TOUCH_FINGERS);
166 ReloadSockets();
167}
168
169Client::~Client() {
170 Reset();
171}
172
173Client::ClientConnection::ClientConnection() = default;
174
175Client::ClientConnection::~ClientConnection() = default;
176
177std::vector<Common::ParamPackage> Client::GetInputDevices() const {
178 std::vector<Common::ParamPackage> devices;
179 for (std::size_t pad = 0; pad < pads.size(); pad++) {
180 if (!DeviceConnected(pad)) {
181 continue;
182 }
183 std::string name = fmt::format("UDP Controller {}", pad);
184 devices.emplace_back(Common::ParamPackage{
185 {"class", "cemuhookudp"},
186 {"display", std::move(name)},
187 {"port", std::to_string(pad)},
188 });
189 }
190 return devices;
191}
192
193bool Client::DeviceConnected(std::size_t pad) const {
194 // Use last timestamp to detect if the socket has stopped sending data
195 const auto now = std::chrono::steady_clock::now();
196 const auto time_difference = static_cast<u64>(
197 std::chrono::duration_cast<std::chrono::milliseconds>(now - pads[pad].last_update).count());
198 return time_difference < 1000 && pads[pad].connected;
199}
200
201void Client::ReloadSockets() {
202 Reset();
203
204 std::stringstream servers_ss(static_cast<std::string>(Settings::values.udp_input_servers));
205 std::string server_token;
206 std::size_t client = 0;
207 while (std::getline(servers_ss, server_token, ',')) {
208 if (client == MAX_UDP_CLIENTS) {
209 break;
210 }
211 std::stringstream server_ss(server_token);
212 std::string token;
213 std::getline(server_ss, token, ':');
214 std::string udp_input_address = token;
215 std::getline(server_ss, token, ':');
216 char* temp;
217 const u16 udp_input_port = static_cast<u16>(std::strtol(token.c_str(), &temp, 0));
218 if (*temp != '\0') {
219 LOG_ERROR(Input, "Port number is not valid {}", token);
220 continue;
221 }
222
223 const std::size_t client_number = GetClientNumber(udp_input_address, udp_input_port);
224 if (client_number != MAX_UDP_CLIENTS) {
225 LOG_ERROR(Input, "Duplicated UDP servers found");
226 continue;
227 }
228 StartCommunication(client++, udp_input_address, udp_input_port);
229 }
230}
231
232std::size_t Client::GetClientNumber(std::string_view host, u16 port) const {
233 for (std::size_t client = 0; client < clients.size(); client++) {
234 if (clients[client].active == -1) {
235 continue;
236 }
237 if (clients[client].host == host && clients[client].port == port) {
238 return client;
239 }
240 }
241 return MAX_UDP_CLIENTS;
242}
243
244void Client::OnVersion([[maybe_unused]] Response::Version data) {
245 LOG_TRACE(Input, "Version packet received: {}", data.version);
246}
247
248void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) {
249 LOG_TRACE(Input, "PortInfo packet received: {}", data.model);
250}
251
252void Client::OnPadData(Response::PadData data, std::size_t client) {
253 const std::size_t pad_index = (client * PADS_PER_CLIENT) + data.info.id;
254
255 if (pad_index >= pads.size()) {
256 LOG_ERROR(Input, "Invalid pad id {}", data.info.id);
257 return;
258 }
259
260 LOG_TRACE(Input, "PadData packet received");
261 if (data.packet_counter == pads[pad_index].packet_sequence) {
262 LOG_WARNING(
263 Input,
264 "PadData packet dropped because its stale info. Current count: {} Packet count: {}",
265 pads[pad_index].packet_sequence, data.packet_counter);
266 pads[pad_index].connected = false;
267 return;
268 }
269
270 clients[client].active = 1;
271 pads[pad_index].connected = true;
272 pads[pad_index].packet_sequence = data.packet_counter;
273
274 const auto now = std::chrono::steady_clock::now();
275 const auto time_difference = static_cast<u64>(
276 std::chrono::duration_cast<std::chrono::microseconds>(now - pads[pad_index].last_update)
277 .count());
278 pads[pad_index].last_update = now;
279
280 const Common::Vec3f raw_gyroscope = {data.gyro.pitch, data.gyro.roll, -data.gyro.yaw};
281 pads[pad_index].motion.SetAcceleration({data.accel.x, -data.accel.z, data.accel.y});
282 // Gyroscope values are not it the correct scale from better joy.
283 // Dividing by 312 allows us to make one full turn = 1 turn
284 // This must be a configurable valued called sensitivity
285 pads[pad_index].motion.SetGyroscope(raw_gyroscope / 312.0f);
286 pads[pad_index].motion.UpdateRotation(time_difference);
287 pads[pad_index].motion.UpdateOrientation(time_difference);
288
289 {
290 std::lock_guard guard(pads[pad_index].status.update_mutex);
291 pads[pad_index].status.motion_status = pads[pad_index].motion.GetMotion();
292
293 for (std::size_t id = 0; id < data.touch.size(); ++id) {
294 UpdateTouchInput(data.touch[id], client, id);
295 }
296
297 if (configuring) {
298 const Common::Vec3f gyroscope = pads[pad_index].motion.GetGyroscope();
299 const Common::Vec3f accelerometer = pads[pad_index].motion.GetAcceleration();
300 UpdateYuzuSettings(client, data.info.id, accelerometer, gyroscope);
301 }
302 }
303}
304
305void Client::StartCommunication(std::size_t client, const std::string& host, u16 port) {
306 SocketCallback callback{[this](Response::Version version) { OnVersion(version); },
307 [this](Response::PortInfo info) { OnPortInfo(info); },
308 [this, client](Response::PadData data) { OnPadData(data, client); }};
309 LOG_INFO(Input, "Starting communication with UDP input server on {}:{}", host, port);
310 clients[client].host = host;
311 clients[client].port = port;
312 clients[client].active = 0;
313 clients[client].socket = std::make_unique<Socket>(host, port, callback);
314 clients[client].thread = std::thread{SocketLoop, clients[client].socket.get()};
315
316 // Set motion parameters
317 // SetGyroThreshold value should be dependent on GyroscopeZeroDriftMode
318 // Real HW values are unknown, 0.0001 is an approximate to Standard
319 for (std::size_t pad = 0; pad < PADS_PER_CLIENT; pad++) {
320 pads[client * PADS_PER_CLIENT + pad].motion.SetGyroThreshold(0.0001f);
321 }
322}
323
324void Client::Reset() {
325 for (auto& client : clients) {
326 if (client.thread.joinable()) {
327 client.active = -1;
328 client.socket->Stop();
329 client.thread.join();
330 }
331 }
332}
333
334void Client::UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
335 const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro) {
336 if (gyro.Length() > 0.2f) {
337 LOG_DEBUG(Input, "UDP Controller {}: gyro=({}, {}, {}), accel=({}, {}, {})", client,
338 gyro[0], gyro[1], gyro[2], acc[0], acc[1], acc[2]);
339 }
340 UDPPadStatus pad{
341 .host = clients[client].host,
342 .port = clients[client].port,
343 .pad_index = pad_index,
344 };
345 for (std::size_t i = 0; i < 3; ++i) {
346 if (gyro[i] > 5.0f || gyro[i] < -5.0f) {
347 pad.motion = static_cast<PadMotion>(i);
348 pad.motion_value = gyro[i];
349 pad_queue.Push(pad);
350 }
351 if (acc[i] > 1.75f || acc[i] < -1.75f) {
352 pad.motion = static_cast<PadMotion>(i + 3);
353 pad.motion_value = acc[i];
354 pad_queue.Push(pad);
355 }
356 }
357}
358
359std::optional<std::size_t> Client::GetUnusedFingerID() const {
360 std::size_t first_free_id = 0;
361 while (first_free_id < MAX_TOUCH_FINGERS) {
362 if (!std::get<2>(touch_status[first_free_id])) {
363 return first_free_id;
364 } else {
365 first_free_id++;
366 }
367 }
368 return std::nullopt;
369}
370
371void Client::UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id) {
372 // TODO: Use custom calibration per device
373 const Common::ParamPackage touch_param(Settings::values.touch_device.GetValue());
374 const u16 min_x = static_cast<u16>(touch_param.Get("min_x", 100));
375 const u16 min_y = static_cast<u16>(touch_param.Get("min_y", 50));
376 const u16 max_x = static_cast<u16>(touch_param.Get("max_x", 1800));
377 const u16 max_y = static_cast<u16>(touch_param.Get("max_y", 850));
378 const std::size_t touch_id = client * 2 + id;
379 if (touch_pad.is_active) {
380 if (finger_id[touch_id] == MAX_TOUCH_FINGERS) {
381 const auto first_free_id = GetUnusedFingerID();
382 if (!first_free_id) {
383 // Invalid finger id skip to next input
384 return;
385 }
386 finger_id[touch_id] = *first_free_id;
387 }
388 auto& [x, y, pressed] = touch_status[finger_id[touch_id]];
389 x = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.x), min_x, max_x) - min_x) /
390 static_cast<float>(max_x - min_x);
391 y = static_cast<float>(std::clamp(static_cast<u16>(touch_pad.y), min_y, max_y) - min_y) /
392 static_cast<float>(max_y - min_y);
393 pressed = true;
394 return;
395 }
396
397 if (finger_id[touch_id] != MAX_TOUCH_FINGERS) {
398 touch_status[finger_id[touch_id]] = {};
399 finger_id[touch_id] = MAX_TOUCH_FINGERS;
400 }
401}
402
403void Client::BeginConfiguration() {
404 pad_queue.Clear();
405 configuring = true;
406}
407
408void Client::EndConfiguration() {
409 pad_queue.Clear();
410 configuring = false;
411}
412
413DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) {
414 const std::size_t client_number = GetClientNumber(host, port);
415 if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
416 return pads[0].status;
417 }
418 return pads[(client_number * PADS_PER_CLIENT) + pad].status;
419}
420
421const DeviceStatus& Client::GetPadState(const std::string& host, u16 port, std::size_t pad) const {
422 const std::size_t client_number = GetClientNumber(host, port);
423 if (client_number == MAX_UDP_CLIENTS || pad >= PADS_PER_CLIENT) {
424 return pads[0].status;
425 }
426 return pads[(client_number * PADS_PER_CLIENT) + pad].status;
427}
428
429Input::TouchStatus& Client::GetTouchState() {
430 return touch_status;
431}
432
433const Input::TouchStatus& Client::GetTouchState() const {
434 return touch_status;
435}
436
437Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() {
438 return pad_queue;
439}
440
441const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const {
442 return pad_queue;
443}
444
445void TestCommunication(const std::string& host, u16 port,
446 const std::function<void()>& success_callback,
447 const std::function<void()>& failure_callback) {
448 std::thread([=] {
449 Common::Event success_event;
450 SocketCallback callback{
451 .version = [](Response::Version) {},
452 .port_info = [](Response::PortInfo) {},
453 .pad_data = [&](Response::PadData) { success_event.Set(); },
454 };
455 Socket socket{host, port, std::move(callback)};
456 std::thread worker_thread{SocketLoop, &socket};
457 const bool result =
458 success_event.WaitUntil(std::chrono::steady_clock::now() + std::chrono::seconds(10));
459 socket.Stop();
460 worker_thread.join();
461 if (result) {
462 success_callback();
463 } else {
464 failure_callback();
465 }
466 }).detach();
467}
468
469CalibrationConfigurationJob::CalibrationConfigurationJob(
470 const std::string& host, u16 port, std::function<void(Status)> status_callback,
471 std::function<void(u16, u16, u16, u16)> data_callback) {
472
473 std::thread([=, this] {
474 Status current_status{Status::Initialized};
475 SocketCallback callback{
476 [](Response::Version) {}, [](Response::PortInfo) {},
477 [&](Response::PadData data) {
478 static constexpr u16 CALIBRATION_THRESHOLD = 100;
479 static constexpr u16 MAX_VALUE = UINT16_MAX;
480
481 if (current_status == Status::Initialized) {
482 // Receiving data means the communication is ready now
483 current_status = Status::Ready;
484 status_callback(current_status);
485 }
486 const auto& touchpad_0 = data.touch[0];
487 if (touchpad_0.is_active == 0) {
488 return;
489 }
490 LOG_DEBUG(Input, "Current touch: {} {}", touchpad_0.x, touchpad_0.y);
491 const u16 min_x = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.x));
492 const u16 min_y = std::min(MAX_VALUE, static_cast<u16>(touchpad_0.y));
493 if (current_status == Status::Ready) {
494 // First touch - min data (min_x/min_y)
495 current_status = Status::Stage1Completed;
496 status_callback(current_status);
497 }
498 if (touchpad_0.x - min_x > CALIBRATION_THRESHOLD &&
499 touchpad_0.y - min_y > CALIBRATION_THRESHOLD) {
500 // Set the current position as max value and finishes configuration
501 const u16 max_x = touchpad_0.x;
502 const u16 max_y = touchpad_0.y;
503 current_status = Status::Completed;
504 data_callback(min_x, min_y, max_x, max_y);
505 status_callback(current_status);
506
507 complete_event.Set();
508 }
509 }};
510 Socket socket{host, port, std::move(callback)};
511 std::thread worker_thread{SocketLoop, &socket};
512 complete_event.Wait();
513 socket.Stop();
514 worker_thread.join();
515 }).detach();
516}
517
518CalibrationConfigurationJob::~CalibrationConfigurationJob() {
519 Stop();
520}
521
522void CalibrationConfigurationJob::Stop() {
523 complete_event.Set();
524}
525
526} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/client.h b/src/input_common/udp/client.h
deleted file mode 100644
index 380f9bb76..000000000
--- a/src/input_common/udp/client.h
+++ /dev/null
@@ -1,183 +0,0 @@
1// Copyright 2018 Citra Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <functional>
8#include <memory>
9#include <mutex>
10#include <optional>
11#include <string>
12#include <thread>
13#include <tuple>
14#include "common/common_types.h"
15#include "common/param_package.h"
16#include "common/thread.h"
17#include "common/threadsafe_queue.h"
18#include "common/vector_math.h"
19#include "core/frontend/input.h"
20#include "input_common/motion_input.h"
21
22namespace InputCommon::CemuhookUDP {
23
24class Socket;
25
26namespace Response {
27struct PadData;
28struct PortInfo;
29struct TouchPad;
30struct Version;
31} // namespace Response
32
33enum class PadMotion {
34 GyroX,
35 GyroY,
36 GyroZ,
37 AccX,
38 AccY,
39 AccZ,
40 Undefined,
41};
42
43enum class PadTouch {
44 Click,
45 Undefined,
46};
47
48struct UDPPadStatus {
49 std::string host{"127.0.0.1"};
50 u16 port{26760};
51 std::size_t pad_index{};
52 PadMotion motion{PadMotion::Undefined};
53 f32 motion_value{0.0f};
54};
55
56struct DeviceStatus {
57 std::mutex update_mutex;
58 Input::MotionStatus motion_status;
59 std::tuple<float, float, bool> touch_status;
60
61 // calibration data for scaling the device's touch area to 3ds
62 struct CalibrationData {
63 u16 min_x{};
64 u16 min_y{};
65 u16 max_x{};
66 u16 max_y{};
67 };
68 std::optional<CalibrationData> touch_calibration;
69};
70
71class Client {
72public:
73 // Initialize the UDP client capture and read sequence
74 Client();
75
76 // Close and release the client
77 ~Client();
78
79 // Used for polling
80 void BeginConfiguration();
81 void EndConfiguration();
82
83 std::vector<Common::ParamPackage> GetInputDevices() const;
84
85 bool DeviceConnected(std::size_t pad) const;
86 void ReloadSockets();
87
88 Common::SPSCQueue<UDPPadStatus>& GetPadQueue();
89 const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const;
90
91 DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad);
92 const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const;
93
94 Input::TouchStatus& GetTouchState();
95 const Input::TouchStatus& GetTouchState() const;
96
97private:
98 struct PadData {
99 std::size_t pad_index{};
100 bool connected{};
101 DeviceStatus status;
102 u64 packet_sequence{};
103
104 // Realtime values
105 // motion is initalized with PID values for drift correction on joycons
106 InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
107 std::chrono::time_point<std::chrono::steady_clock> last_update;
108 };
109
110 struct ClientConnection {
111 ClientConnection();
112 ~ClientConnection();
113 std::string host{"127.0.0.1"};
114 u16 port{26760};
115 s8 active{-1};
116 std::unique_ptr<Socket> socket;
117 std::thread thread;
118 };
119
120 // For shutting down, clear all data, join all threads, release usb
121 void Reset();
122
123 // Translates configuration to client number
124 std::size_t GetClientNumber(std::string_view host, u16 port) const;
125
126 void OnVersion(Response::Version);
127 void OnPortInfo(Response::PortInfo);
128 void OnPadData(Response::PadData, std::size_t client);
129 void StartCommunication(std::size_t client, const std::string& host, u16 port);
130 void UpdateYuzuSettings(std::size_t client, std::size_t pad_index,
131 const Common::Vec3<float>& acc, const Common::Vec3<float>& gyro);
132
133 // Returns an unused finger id, if there is no fingers available std::nullopt will be
134 // returned
135 std::optional<std::size_t> GetUnusedFingerID() const;
136
137 // Merges and updates all touch inputs into the touch_status array
138 void UpdateTouchInput(Response::TouchPad& touch_pad, std::size_t client, std::size_t id);
139
140 bool configuring = false;
141
142 // Allocate clients for 8 udp servers
143 static constexpr std::size_t MAX_UDP_CLIENTS = 8;
144 static constexpr std::size_t PADS_PER_CLIENT = 4;
145 // Each client can have up 2 touch inputs
146 static constexpr std::size_t MAX_TOUCH_FINGERS = MAX_UDP_CLIENTS * 2;
147 std::array<PadData, MAX_UDP_CLIENTS * PADS_PER_CLIENT> pads{};
148 std::array<ClientConnection, MAX_UDP_CLIENTS> clients{};
149 Common::SPSCQueue<UDPPadStatus> pad_queue{};
150 Input::TouchStatus touch_status{};
151 std::array<std::size_t, MAX_TOUCH_FINGERS> finger_id{};
152};
153
154/// An async job allowing configuration of the touchpad calibration.
155class CalibrationConfigurationJob {
156public:
157 enum class Status {
158 Initialized,
159 Ready,
160 Stage1Completed,
161 Completed,
162 };
163 /**
164 * Constructs and starts the job with the specified parameter.
165 *
166 * @param status_callback Callback for job status updates
167 * @param data_callback Called when calibration data is ready
168 */
169 explicit CalibrationConfigurationJob(const std::string& host, u16 port,
170 std::function<void(Status)> status_callback,
171 std::function<void(u16, u16, u16, u16)> data_callback);
172 ~CalibrationConfigurationJob();
173 void Stop();
174
175private:
176 Common::Event complete_event;
177};
178
179void TestCommunication(const std::string& host, u16 port,
180 const std::function<void()>& success_callback,
181 const std::function<void()>& failure_callback);
182
183} // namespace InputCommon::CemuhookUDP
diff --git a/src/input_common/udp/udp.cpp b/src/input_common/udp/udp.cpp
deleted file mode 100644
index 9829da6f0..000000000
--- a/src/input_common/udp/udp.cpp
+++ /dev/null
@@ -1,110 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#include <mutex>
6#include <utility>
7#include "common/assert.h"
8#include "common/threadsafe_queue.h"
9#include "input_common/udp/client.h"
10#include "input_common/udp/udp.h"
11
12namespace InputCommon {
13
14class UDPMotion final : public Input::MotionDevice {
15public:
16 explicit UDPMotion(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
17 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
18
19 Input::MotionStatus GetStatus() const override {
20 return client->GetPadState(ip, port, pad).motion_status;
21 }
22
23private:
24 const std::string ip;
25 const u16 port;
26 const u16 pad;
27 CemuhookUDP::Client* client;
28 mutable std::mutex mutex;
29};
30
31/// A motion device factory that creates motion devices from a UDP client
32UDPMotionFactory::UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_)
33 : client(std::move(client_)) {}
34
35/**
36 * Creates motion device
37 * @param params contains parameters for creating the device:
38 * - "port": the UDP port number
39 */
40std::unique_ptr<Input::MotionDevice> UDPMotionFactory::Create(const Common::ParamPackage& params) {
41 auto ip = params.Get("ip", "127.0.0.1");
42 const auto port = static_cast<u16>(params.Get("port", 26760));
43 const auto pad = static_cast<u16>(params.Get("pad_index", 0));
44
45 return std::make_unique<UDPMotion>(std::move(ip), port, pad, client.get());
46}
47
48void UDPMotionFactory::BeginConfiguration() {
49 polling = true;
50 client->BeginConfiguration();
51}
52
53void UDPMotionFactory::EndConfiguration() {
54 polling = false;
55 client->EndConfiguration();
56}
57
58Common::ParamPackage UDPMotionFactory::GetNextInput() {
59 Common::ParamPackage params;
60 CemuhookUDP::UDPPadStatus pad;
61 auto& queue = client->GetPadQueue();
62 while (queue.Pop(pad)) {
63 if (pad.motion == CemuhookUDP::PadMotion::Undefined || std::abs(pad.motion_value) < 1) {
64 continue;
65 }
66 params.Set("engine", "cemuhookudp");
67 params.Set("ip", pad.host);
68 params.Set("port", static_cast<u16>(pad.port));
69 params.Set("pad_index", static_cast<u16>(pad.pad_index));
70 params.Set("motion", static_cast<u16>(pad.motion));
71 return params;
72 }
73 return params;
74}
75
76class UDPTouch final : public Input::TouchDevice {
77public:
78 explicit UDPTouch(std::string ip_, u16 port_, u16 pad_, CemuhookUDP::Client* client_)
79 : ip(std::move(ip_)), port(port_), pad(pad_), client(client_) {}
80
81 Input::TouchStatus GetStatus() const override {
82 return client->GetTouchState();
83 }
84
85private:
86 const std::string ip;
87 [[maybe_unused]] const u16 port;
88 [[maybe_unused]] const u16 pad;
89 CemuhookUDP::Client* client;
90 mutable std::mutex mutex;
91};
92
93/// A motion device factory that creates motion devices from a UDP client
94UDPTouchFactory::UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_)
95 : client(std::move(client_)) {}
96
97/**
98 * Creates motion device
99 * @param params contains parameters for creating the device:
100 * - "port": the UDP port number
101 */
102std::unique_ptr<Input::TouchDevice> UDPTouchFactory::Create(const Common::ParamPackage& params) {
103 auto ip = params.Get("ip", "127.0.0.1");
104 const auto port = static_cast<u16>(params.Get("port", 26760));
105 const auto pad = static_cast<u16>(params.Get("pad_index", 0));
106
107 return std::make_unique<UDPTouch>(std::move(ip), port, pad, client.get());
108}
109
110} // namespace InputCommon
diff --git a/src/input_common/udp/udp.h b/src/input_common/udp/udp.h
deleted file mode 100644
index ea3fd4175..000000000
--- a/src/input_common/udp/udp.h
+++ /dev/null
@@ -1,57 +0,0 @@
1// Copyright 2020 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#pragma once
6
7#include <memory>
8#include "core/frontend/input.h"
9#include "input_common/udp/client.h"
10
11namespace InputCommon {
12
13/// A motion device factory that creates motion devices from udp clients
14class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> {
15public:
16 explicit UDPMotionFactory(std::shared_ptr<CemuhookUDP::Client> client_);
17
18 std::unique_ptr<Input::MotionDevice> Create(const Common::ParamPackage& params) override;
19
20 Common::ParamPackage GetNextInput();
21
22 /// For device input configuration/polling
23 void BeginConfiguration();
24 void EndConfiguration();
25
26 bool IsPolling() const {
27 return polling;
28 }
29
30private:
31 std::shared_ptr<CemuhookUDP::Client> client;
32 bool polling = false;
33};
34
35/// A touch device factory that creates touch devices from udp clients
36class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> {
37public:
38 explicit UDPTouchFactory(std::shared_ptr<CemuhookUDP::Client> client_);
39
40 std::unique_ptr<Input::TouchDevice> Create(const Common::ParamPackage& params) override;
41
42 Common::ParamPackage GetNextInput();
43
44 /// For device input configuration/polling
45 void BeginConfiguration();
46 void EndConfiguration();
47
48 bool IsPolling() const {
49 return polling;
50 }
51
52private:
53 std::shared_ptr<CemuhookUDP::Client> client;
54 bool polling = false;
55};
56
57} // namespace InputCommon