diff options
Diffstat (limited to 'src/input_common/udp')
| -rw-r--r-- | src/input_common/udp/client.cpp | 526 | ||||
| -rw-r--r-- | src/input_common/udp/client.h | 183 | ||||
| -rw-r--r-- | src/input_common/udp/udp.cpp | 110 | ||||
| -rw-r--r-- | src/input_common/udp/udp.h | 57 |
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 | |||
| 16 | using boost::asio::ip::udp; | ||
| 17 | |||
| 18 | namespace InputCommon::CemuhookUDP { | ||
| 19 | |||
| 20 | struct 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 | |||
| 26 | class Socket { | ||
| 27 | public: | ||
| 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 | |||
| 64 | private: | ||
| 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 | |||
| 157 | static void SocketLoop(Socket* socket) { | ||
| 158 | socket->StartReceive(); | ||
| 159 | socket->StartSend(Socket::clock::now()); | ||
| 160 | socket->Loop(); | ||
| 161 | } | ||
| 162 | |||
| 163 | Client::Client() { | ||
| 164 | LOG_INFO(Input, "Udp Initialization started"); | ||
| 165 | finger_id.fill(MAX_TOUCH_FINGERS); | ||
| 166 | ReloadSockets(); | ||
| 167 | } | ||
| 168 | |||
| 169 | Client::~Client() { | ||
| 170 | Reset(); | ||
| 171 | } | ||
| 172 | |||
| 173 | Client::ClientConnection::ClientConnection() = default; | ||
| 174 | |||
| 175 | Client::ClientConnection::~ClientConnection() = default; | ||
| 176 | |||
| 177 | std::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 | |||
| 193 | bool 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 | |||
| 201 | void 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 | |||
| 232 | std::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 | |||
| 244 | void Client::OnVersion([[maybe_unused]] Response::Version data) { | ||
| 245 | LOG_TRACE(Input, "Version packet received: {}", data.version); | ||
| 246 | } | ||
| 247 | |||
| 248 | void Client::OnPortInfo([[maybe_unused]] Response::PortInfo data) { | ||
| 249 | LOG_TRACE(Input, "PortInfo packet received: {}", data.model); | ||
| 250 | } | ||
| 251 | |||
| 252 | void 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 | |||
| 305 | void 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 | |||
| 324 | void 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 | |||
| 334 | void 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 | |||
| 359 | std::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 | |||
| 371 | void 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 | |||
| 403 | void Client::BeginConfiguration() { | ||
| 404 | pad_queue.Clear(); | ||
| 405 | configuring = true; | ||
| 406 | } | ||
| 407 | |||
| 408 | void Client::EndConfiguration() { | ||
| 409 | pad_queue.Clear(); | ||
| 410 | configuring = false; | ||
| 411 | } | ||
| 412 | |||
| 413 | DeviceStatus& 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 | |||
| 421 | const 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 | |||
| 429 | Input::TouchStatus& Client::GetTouchState() { | ||
| 430 | return touch_status; | ||
| 431 | } | ||
| 432 | |||
| 433 | const Input::TouchStatus& Client::GetTouchState() const { | ||
| 434 | return touch_status; | ||
| 435 | } | ||
| 436 | |||
| 437 | Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() { | ||
| 438 | return pad_queue; | ||
| 439 | } | ||
| 440 | |||
| 441 | const Common::SPSCQueue<UDPPadStatus>& Client::GetPadQueue() const { | ||
| 442 | return pad_queue; | ||
| 443 | } | ||
| 444 | |||
| 445 | void 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 | |||
| 469 | CalibrationConfigurationJob::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 | |||
| 518 | CalibrationConfigurationJob::~CalibrationConfigurationJob() { | ||
| 519 | Stop(); | ||
| 520 | } | ||
| 521 | |||
| 522 | void 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 | |||
| 22 | namespace InputCommon::CemuhookUDP { | ||
| 23 | |||
| 24 | class Socket; | ||
| 25 | |||
| 26 | namespace Response { | ||
| 27 | struct PadData; | ||
| 28 | struct PortInfo; | ||
| 29 | struct TouchPad; | ||
| 30 | struct Version; | ||
| 31 | } // namespace Response | ||
| 32 | |||
| 33 | enum class PadMotion { | ||
| 34 | GyroX, | ||
| 35 | GyroY, | ||
| 36 | GyroZ, | ||
| 37 | AccX, | ||
| 38 | AccY, | ||
| 39 | AccZ, | ||
| 40 | Undefined, | ||
| 41 | }; | ||
| 42 | |||
| 43 | enum class PadTouch { | ||
| 44 | Click, | ||
| 45 | Undefined, | ||
| 46 | }; | ||
| 47 | |||
| 48 | struct 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 | |||
| 56 | struct 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 | |||
| 71 | class Client { | ||
| 72 | public: | ||
| 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 | |||
| 97 | private: | ||
| 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. | ||
| 155 | class CalibrationConfigurationJob { | ||
| 156 | public: | ||
| 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 | |||
| 175 | private: | ||
| 176 | Common::Event complete_event; | ||
| 177 | }; | ||
| 178 | |||
| 179 | void 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 | |||
| 12 | namespace InputCommon { | ||
| 13 | |||
| 14 | class UDPMotion final : public Input::MotionDevice { | ||
| 15 | public: | ||
| 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 | |||
| 23 | private: | ||
| 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 | ||
| 32 | UDPMotionFactory::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 | */ | ||
| 40 | std::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 | |||
| 48 | void UDPMotionFactory::BeginConfiguration() { | ||
| 49 | polling = true; | ||
| 50 | client->BeginConfiguration(); | ||
| 51 | } | ||
| 52 | |||
| 53 | void UDPMotionFactory::EndConfiguration() { | ||
| 54 | polling = false; | ||
| 55 | client->EndConfiguration(); | ||
| 56 | } | ||
| 57 | |||
| 58 | Common::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 | |||
| 76 | class UDPTouch final : public Input::TouchDevice { | ||
| 77 | public: | ||
| 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 | |||
| 85 | private: | ||
| 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 | ||
| 94 | UDPTouchFactory::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 | */ | ||
| 102 | std::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 | |||
| 11 | namespace InputCommon { | ||
| 12 | |||
| 13 | /// A motion device factory that creates motion devices from udp clients | ||
| 14 | class UDPMotionFactory final : public Input::Factory<Input::MotionDevice> { | ||
| 15 | public: | ||
| 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 | |||
| 30 | private: | ||
| 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 | ||
| 36 | class UDPTouchFactory final : public Input::Factory<Input::TouchDevice> { | ||
| 37 | public: | ||
| 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 | |||
| 52 | private: | ||
| 53 | std::shared_ptr<CemuhookUDP::Client> client; | ||
| 54 | bool polling = false; | ||
| 55 | }; | ||
| 56 | |||
| 57 | } // namespace InputCommon | ||