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 constexpr char DEFAULT_SRV[] = "127.0.0.1:26760";
25 
26 class Socket;
27 
28 namespace Response {
29 struct PadData;
30 struct PortInfo;
31 struct Version;
32 } // namespace Response
33 
34 enum class PadMotion {
35     GyroX,
36     GyroY,
37     GyroZ,
38     AccX,
39     AccY,
40     AccZ,
41     Undefined,
42 };
43 
44 enum class PadTouch {
45     Click,
46     Undefined,
47 };
48 
49 struct UDPPadStatus {
50     std::string host{"127.0.0.1"};
51     u16 port{26760};
52     std::size_t pad_index{};
53     PadTouch touch{PadTouch::Undefined};
54     PadMotion motion{PadMotion::Undefined};
55     f32 motion_value{0.0f};
56 };
57 
58 struct DeviceStatus {
59     std::mutex update_mutex;
60     Input::MotionStatus motion_status;
61     std::tuple<float, float, bool> touch_status;
62 
63     // calibration data for scaling the device's touch area to 3ds
64     struct CalibrationData {
65         u16 min_x{};
66         u16 min_y{};
67         u16 max_x{};
68         u16 max_y{};
69     };
70     std::optional<CalibrationData> touch_calibration;
71 };
72 
73 class Client {
74 public:
75     // Initialize the UDP client capture and read sequence
76     Client();
77 
78     // Close and release the client
79     ~Client();
80 
81     // Used for polling
82     void BeginConfiguration();
83     void EndConfiguration();
84 
85     std::vector<Common::ParamPackage> GetInputDevices() const;
86 
87     bool DeviceConnected(std::size_t client) const;
88     void ReloadSockets();
89 
90     Common::SPSCQueue<UDPPadStatus>& GetPadQueue();
91     const Common::SPSCQueue<UDPPadStatus>& GetPadQueue() const;
92 
93     DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad);
94     const DeviceStatus& GetPadState(const std::string& host, u16 port, std::size_t pad) const;
95 
96 private:
97     struct ClientData {
98         std::string host{"127.0.0.1"};
99         u16 port{26760};
100         std::size_t pad_index{};
101         std::unique_ptr<Socket> socket;
102         DeviceStatus status;
103         std::thread thread;
104         u64 packet_sequence{};
105         s8 active{-1};
106 
107         // Realtime values
108         // motion is initalized with PID values for drift correction on joycons
109         InputCommon::MotionInput motion{0.3f, 0.005f, 0.0f};
110         std::chrono::time_point<std::chrono::steady_clock> last_motion_update;
111     };
112 
113     // For shutting down, clear all data, join all threads, release usb
114     void Reset();
115 
116     // Translates configuration to client number
117     std::size_t GetClientNumber(std::string_view host, u16 port, std::size_t pad) const;
118 
119     void OnVersion(Response::Version);
120     void OnPortInfo(Response::PortInfo);
121     void OnPadData(Response::PadData, std::size_t client);
122     void StartCommunication(std::size_t client, const std::string& host, u16 port,
123                             std::size_t pad_index, u32 client_id);
124     void UpdateYuzuSettings(std::size_t client, const Common::Vec3<float>& acc,
125                             const Common::Vec3<float>& gyro, bool touch);
126 
127     bool configuring = false;
128 
129     // Allocate clients for 8 udp servers
130     const std::size_t max_udp_clients = 32;
131     std::array<ClientData, 4 * 8> clients;
132     Common::SPSCQueue<UDPPadStatus> pad_queue;
133 };
134 
135 /// An async job allowing configuration of the touchpad calibration.
136 class CalibrationConfigurationJob {
137 public:
138     enum class Status {
139         Initialized,
140         Ready,
141         Stage1Completed,
142         Completed,
143     };
144     /**
145      * Constructs and starts the job with the specified parameter.
146      *
147      * @param status_callback Callback for job status updates
148      * @param data_callback Called when calibration data is ready
149      */
150     explicit CalibrationConfigurationJob(const std::string& host, u16 port, std::size_t pad_index,
151                                          u32 client_id, std::function<void(Status)> status_callback,
152                                          std::function<void(u16, u16, u16, u16)> data_callback);
153     ~CalibrationConfigurationJob();
154     void Stop();
155 
156 private:
157     Common::Event complete_event;
158 };
159 
160 void TestCommunication(const std::string& host, u16 port, std::size_t pad_index, u32 client_id,
161                        const std::function<void()>& success_callback,
162                        const std::function<void()>& failure_callback);
163 
164 } // namespace InputCommon::CemuhookUDP
165