1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
|
// Copyright 2019 Dolphin Emulator Project
// Licensed under GPLv2+
// Refer to the license.txt file included.
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPClient.h"
#include <algorithm>
#include <array>
#include <chrono>
#include <mutex>
#include <tuple>
#include <SFML/Network/SocketSelector.hpp>
#include <SFML/Network/UdpSocket.hpp>
#include <fmt/format.h>
#include "Common/Config/Config.h"
#include "Common/Flag.h"
#include "Common/Logging/Log.h"
#include "Common/MathUtil.h"
#include "Common/Random.h"
#include "Common/StringUtil.h"
#include "Common/Thread.h"
#include "Core/CoreTiming.h"
#include "InputCommon/ControllerInterface/ControllerInterface.h"
#include "InputCommon/ControllerInterface/DualShockUDPClient/DualShockUDPProto.h"
namespace ciface::DualShockUDPClient
{
constexpr std::string_view DUALSHOCKUDP_SOURCE_NAME = "DSUClient";
namespace Settings
{
const Config::Info<std::string> SERVER_ADDRESS{
{Config::System::DualShockUDPClient, "Server", "IPAddress"}, ""};
const Config::Info<int> SERVER_PORT{{Config::System::DualShockUDPClient, "Server", "Port"}, 0};
const Config::Info<std::string> SERVERS{{Config::System::DualShockUDPClient, "Server", "Entries"},
""};
const Config::Info<bool> SERVERS_ENABLED{{Config::System::DualShockUDPClient, "Server", "Enabled"},
false};
} // namespace Settings
// Clock type used for querying timeframes
using SteadyClock = std::chrono::steady_clock;
class Device final : public Core::Device
{
private:
template <class T>
class Button final : public Input
{
public:
Button(const char* name, const T& buttons, T mask)
: m_name(name), m_buttons(buttons), m_mask(mask)
{
}
std::string GetName() const override { return m_name; }
ControlState GetState() const override { return (m_buttons & m_mask) != 0; }
private:
const char* const m_name;
const T& m_buttons;
const T m_mask;
};
template <class T>
class AnalogInput : public Input
{
public:
AnalogInput(const char* name, const T& input, ControlState range, ControlState offset = 0)
: m_name(name), m_input(input), m_range(range), m_offset(offset)
{
}
std::string GetName() const final override { return m_name; }
ControlState GetState() const final override
{
return (ControlState(m_input) + m_offset) / m_range;
}
private:
const char* m_name;
const T& m_input;
const ControlState m_range;
const ControlState m_offset;
};
class TouchInput final : public AnalogInput<int>
{
public:
using AnalogInput::AnalogInput;
bool IsDetectable() const override { return false; }
};
class MotionInput final : public AnalogInput<float>
{
public:
using AnalogInput::AnalogInput;
bool IsDetectable() const override { return false; }
};
using AccelerometerInput = MotionInput;
using GyroInput = MotionInput;
class BatteryInput final : public Input
{
public:
using BatteryState = Proto::DsBattery;
BatteryInput(const BatteryState& battery) : m_battery(battery) {}
std::string GetName() const override { return "Battery"; }
ControlState GetState() const override
{
switch (m_battery)
{
case BatteryState::Charging: // We don't actually know the battery level in this case
case BatteryState::Charged:
return BATTERY_INPUT_MAX_VALUE;
default:
return ControlState(m_battery) / ControlState(BatteryState::Full) * BATTERY_INPUT_MAX_VALUE;
}
}
bool IsDetectable() const override { return false; }
private:
const BatteryState& m_battery;
};
public:
void UpdateInput() override;
Device(std::string name, int index, std::string server_address, u16 server_port);
std::string GetName() const final override;
std::string GetSource() const final override;
std::optional<int> GetPreferredId() const final override;
// Always add these at the end, given their hotplug nature
int GetSortPriority() const override { return -2; }
private:
void ResetPadData();
const std::string m_name;
const int m_index;
sf::UdpSocket m_socket;
SteadyClock::time_point m_next_reregister = SteadyClock::time_point::min();
Proto::MessageType::PadDataResponse m_pad_data{};
Proto::Touch m_prev_touch{};
bool m_prev_touch_valid = false;
int m_touch_x = 0;
int m_touch_y = 0;
std::string m_server_address;
u16 m_server_port;
s16 m_touch_x_min;
s16 m_touch_y_min;
s16 m_touch_x_max;
s16 m_touch_y_max;
};
using MathUtil::GRAVITY_ACCELERATION;
constexpr auto SERVER_REREGISTER_INTERVAL = std::chrono::seconds{1};
constexpr auto SERVER_LISTPORTS_INTERVAL = std::chrono::seconds{1};
constexpr int TOUCH_X_AXIS_MAX = 1000;
constexpr int TOUCH_Y_AXIS_MAX = 500;
constexpr auto THREAD_MAX_WAIT_INTERVAL = std::chrono::milliseconds{250};
constexpr auto SERVER_UNRESPONSIVE_INTERVAL = std::chrono::seconds{1}; // Can be 0
constexpr u32 SERVER_ASKED_PADS = 4;
struct Server
{
Server(std::string description, std::string address, u16 port)
: m_description{std::move(description)}, m_address{std::move(address)}, m_port{port}
{
}
Server(const Server&) = delete;
Server(Server&& other) noexcept
{
m_description = std::move(other.m_description);
m_address = std::move(other.m_address);
m_port = other.m_port;
m_port_info = std::move(other.m_port_info);
}
Server& operator=(const Server&) = delete;
Server& operator=(Server&&) = delete;
~Server() = default;
std::string m_description;
std::string m_address;
u16 m_port;
std::mutex m_port_info_mutex;
std::array<Proto::MessageType::PortInfo, Proto::PORT_COUNT> m_port_info;
sf::UdpSocket m_socket;
SteadyClock::time_point m_disconnect_time = SteadyClock::now();
};
static bool s_servers_enabled;
static std::vector<Server> s_servers;
static u32 s_client_uid;
static SteadyClock::time_point s_next_listports_time;
static std::thread s_hotplug_thread;
static Common::Flag s_hotplug_thread_running;
static bool IsSameController(const Proto::MessageType::PortInfo& a,
const Proto::MessageType::PortInfo& b)
{
// compare everything but battery_status
return std::tie(a.pad_id, a.pad_state, a.model, a.connection_type, a.pad_mac_address) ==
std::tie(b.pad_id, b.pad_state, b.model, b.connection_type, b.pad_mac_address);
}
static void HotplugThreadFunc()
{
Common::SetCurrentThreadName("DualShockUDPClient Hotplug Thread");
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread started");
std::vector<bool> timed_out_servers(s_servers.size(), false);
while (s_hotplug_thread_running.IsSet())
{
using namespace std::chrono;
using namespace std::chrono_literals;
const auto now = SteadyClock::now();
if (now >= s_next_listports_time)
{
s_next_listports_time = now + SERVER_LISTPORTS_INTERVAL;
for (size_t i = 0; i < s_servers.size(); ++i)
{
auto& server = s_servers[i];
Proto::Message<Proto::MessageType::ListPorts> msg(s_client_uid);
auto& list_ports = msg.m_message;
// We ask for x possible devices. We will receive a message for every connected device.
list_ports.pad_request_count = SERVER_ASKED_PADS;
list_ports.pad_ids = {0, 1, 2, 3};
msg.Finish();
if (server.m_socket.send(&list_ports, sizeof list_ports, server.m_address, server.m_port) !=
sf::Socket::Status::Done)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient HotplugThreadFunc send failed");
}
timed_out_servers[i] = true;
}
}
sf::SocketSelector selector;
for (auto& server : s_servers)
{
selector.add(server.m_socket);
}
auto timeout = duration_cast<milliseconds>(s_next_listports_time - SteadyClock::now());
// Receive controller port info within a time from our request.
// Run this even if we sent no new requests, to disconnect devices,
// sleep (wait) the thread and catch old responses.
do
{
// Selector's wait treats a timeout of zero as infinite timeout, which we don't want,
// but we also don't want risk waiting for the whole SERVER_LISTPORTS_INTERVAL and hang
// the thead trying to close this one in case we received no answers.
const auto current_timeout = std::max(std::min(timeout, THREAD_MAX_WAIT_INTERVAL), 1ms);
timeout -= current_timeout;
// This will return at the first answer
if (selector.wait(sf::milliseconds(current_timeout.count())))
{
// Now check all the servers because we don't know which one(s) sent a reply
for (size_t i = 0; i < s_servers.size(); ++i)
{
auto& server = s_servers[i];
if (!selector.isReady(server.m_socket))
{
continue;
}
Proto::Message<Proto::MessageType::FromServer> msg;
std::size_t received_bytes;
sf::IpAddress sender;
u16 port;
if (server.m_socket.receive(&msg, sizeof(msg), received_bytes, sender, port) !=
sf::Socket::Status::Done)
{
continue;
}
if (auto port_info = msg.CheckAndCastTo<Proto::MessageType::PortInfo>())
{
server.m_disconnect_time = SteadyClock::now() + SERVER_UNRESPONSIVE_INTERVAL;
// We have receive at least one valid update, that's enough. This is needed to avoid
// false positive when checking for disconnection in case our thread waited too long
timed_out_servers[i] = false;
const bool port_changed =
!IsSameController(*port_info, server.m_port_info[port_info->pad_id]);
if (port_changed)
{
server.m_port_info[port_info->pad_id] = *port_info;
// Just remove and re-add all the devices for simplicity
g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); });
}
}
}
}
if (!s_hotplug_thread_running.IsSet()) // Avoid hanging the thread for too long
return;
} while (timeout > 0ms);
// If we have failed to receive any information from the server (or even send it),
// disconnect all devices from it (after enough time has elapsed, to avoid false positives).
for (size_t i = 0; i < s_servers.size(); ++i)
{
auto& server = s_servers[i];
if (timed_out_servers[i] && SteadyClock::now() >= server.m_disconnect_time)
{
bool any_connected = false;
for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++)
{
any_connected = any_connected ||
server.m_port_info[port_index].pad_state == Proto::DsState::Connected;
server.m_port_info[port_index] = {};
server.m_port_info[port_index].pad_id = static_cast<u8>(port_index);
}
// We can't only remove devices added by this server as we wouldn't know which they are
if (any_connected)
g_controller_interface.PlatformPopulateDevices([] { PopulateDevices(); });
}
}
}
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient hotplug thread stopped");
}
static void StartHotplugThread()
{
// Mark the thread as running.
if (!s_hotplug_thread_running.TestAndSet())
{
// It was already running.
return;
}
s_hotplug_thread = std::thread(HotplugThreadFunc);
}
static void StopHotplugThread()
{
// Tell the hotplug thread to stop.
if (!s_hotplug_thread_running.TestAndClear())
{
// It wasn't running, we're done.
return;
}
for (auto& server : s_servers)
{
server.m_socket.unbind(); // interrupt blocking socket
}
s_hotplug_thread.join();
}
static void Restart()
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient Restart");
StopHotplugThread();
for (auto& server : s_servers)
{
for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++)
{
server.m_port_info[port_index] = {};
server.m_port_info[port_index].pad_id = static_cast<u8>(port_index);
}
}
PopulateDevices(); // Only removes devices
s_client_uid = Common::Random::GenerateValue<u32>();
s_next_listports_time = SteadyClock::now();
if (s_servers_enabled && !s_servers.empty())
StartHotplugThread();
}
static void ConfigChanged()
{
const bool servers_enabled = Config::Get(Settings::SERVERS_ENABLED);
const std::string servers_setting = Config::Get(Settings::SERVERS);
std::string new_servers_setting;
for (const auto& server : s_servers)
{
new_servers_setting +=
fmt::format("{}:{}:{};", server.m_description, server.m_address, server.m_port);
}
if (servers_enabled != s_servers_enabled || servers_setting != new_servers_setting)
{
s_servers_enabled = servers_enabled;
s_servers.clear();
const auto server_details = SplitString(servers_setting, ';');
for (const auto& server_detail : server_details)
{
const auto server_info = SplitString(server_detail, ':');
if (server_info.size() < 3)
continue;
const std::string description = server_info[0];
const std::string server_address = server_info[1];
const auto port = std::stoi(server_info[2]);
if (port >= std::numeric_limits<u16>::max())
{
continue;
}
u16 server_port = static_cast<u16>(port);
s_servers.emplace_back(description, server_address, server_port);
}
Restart();
}
}
void Init()
{
// The following is added for backwards compatibility
const auto server_address_setting = Config::Get(Settings::SERVER_ADDRESS);
const auto server_port_setting = Config::Get(Settings::SERVER_PORT);
if (!server_address_setting.empty() && server_port_setting != 0)
{
const auto& servers_setting = Config::Get(ciface::DualShockUDPClient::Settings::SERVERS);
Config::SetBaseOrCurrent(ciface::DualShockUDPClient::Settings::SERVERS,
servers_setting + fmt::format("{}:{}:{};", "DS4",
server_address_setting,
server_port_setting));
Config::SetBase(Settings::SERVER_ADDRESS, "");
Config::SetBase(Settings::SERVER_PORT, 0);
}
// It would be much better to unbind from this callback on DeInit but it's not possible as of now
Config::AddConfigChangedCallback(ConfigChanged);
ConfigChanged(); // Call it immediately to load settings
}
void PopulateDevices()
{
INFO_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient PopulateDevices");
// s_servers has already been updated so we can't use it to know which devices we removed,
// also it's good to remove all of them before adding new ones so that their id will be set
// correctly if they have the same name
g_controller_interface.RemoveDevice(
[](const auto* dev) { return dev->GetSource() == DUALSHOCKUDP_SOURCE_NAME; });
for (auto& server : s_servers)
{
std::lock_guard lock{server.m_port_info_mutex};
for (size_t port_index = 0; port_index < server.m_port_info.size(); port_index++)
{
const Proto::MessageType::PortInfo& port_info = server.m_port_info[port_index];
if (port_info.pad_state != Proto::DsState::Connected)
continue;
g_controller_interface.AddDevice(std::make_shared<Device>(
server.m_description, static_cast<int>(port_index), server.m_address, server.m_port));
}
}
}
void DeInit()
{
StopHotplugThread();
}
Device::Device(std::string name, int index, std::string server_address, u16 server_port)
: m_name{std::move(name)}, m_index{index}, m_server_address{std::move(server_address)},
m_server_port{server_port}
{
m_socket.setBlocking(false);
AddInput(new AnalogInput<u8>("Pad W", m_pad_data.button_dpad_left_analog, 255));
AddInput(new AnalogInput<u8>("Pad S", m_pad_data.button_dpad_down_analog, 255));
AddInput(new AnalogInput<u8>("Pad E", m_pad_data.button_dpad_right_analog, 255));
AddInput(new AnalogInput<u8>("Pad N", m_pad_data.button_dpad_up_analog, 255));
AddInput(new AnalogInput<u8>("Square", m_pad_data.button_square_analog, 255));
AddInput(new AnalogInput<u8>("Cross", m_pad_data.button_cross_analog, 255));
AddInput(new AnalogInput<u8>("Circle", m_pad_data.button_circle_analog, 255));
AddInput(new AnalogInput<u8>("Triangle", m_pad_data.button_triangle_analog, 255));
AddInput(new AnalogInput<u8>("L1", m_pad_data.button_l1_analog, 255));
AddInput(new AnalogInput<u8>("R1", m_pad_data.button_r1_analog, 255));
AddInput(new AnalogInput<u8>("L2", m_pad_data.trigger_l2, 255));
AddInput(new AnalogInput<u8>("R2", m_pad_data.trigger_r2, 255));
AddInput(new Button<u8>("L3", m_pad_data.button_states1, 0x2));
AddInput(new Button<u8>("R3", m_pad_data.button_states1, 0x4));
AddInput(new Button<u8>("Share", m_pad_data.button_states1, 0x1));
AddInput(new Button<u8>("Options", m_pad_data.button_states1, 0x8));
AddInput(new Button<u8>("PS", m_pad_data.button_ps, 0x1));
AddInput(new Button<u8>("Touch Button", m_pad_data.button_touch, 0x1));
AddInput(new AnalogInput<u8>("Left X-", m_pad_data.left_stick_x, -128, -128));
AddInput(new AnalogInput<u8>("Left X+", m_pad_data.left_stick_x, 127, -128));
AddInput(new AnalogInput<u8>("Left Y-", m_pad_data.left_stick_y_inverted, -128, -128));
AddInput(new AnalogInput<u8>("Left Y+", m_pad_data.left_stick_y_inverted, 127, -128));
AddInput(new AnalogInput<u8>("Right X-", m_pad_data.right_stick_x, -128, -128));
AddInput(new AnalogInput<u8>("Right X+", m_pad_data.right_stick_x, 127, -128));
AddInput(new AnalogInput<u8>("Right Y-", m_pad_data.right_stick_y_inverted, -128, -128));
AddInput(new AnalogInput<u8>("Right Y+", m_pad_data.right_stick_y_inverted, 127, -128));
AddInput(new TouchInput("Touch X-", m_touch_x, -TOUCH_X_AXIS_MAX));
AddInput(new TouchInput("Touch X+", m_touch_x, TOUCH_X_AXIS_MAX));
AddInput(new TouchInput("Touch Y-", m_touch_y, -TOUCH_Y_AXIS_MAX));
AddInput(new TouchInput("Touch Y+", m_touch_y, TOUCH_Y_AXIS_MAX));
// Convert Gs to meters per second squared
constexpr auto accel_scale = 1.0 / GRAVITY_ACCELERATION;
AddInput(new AccelerometerInput("Accel Up", m_pad_data.accelerometer_y_g, -accel_scale));
AddInput(new AccelerometerInput("Accel Down", m_pad_data.accelerometer_y_g, accel_scale));
AddInput(new AccelerometerInput("Accel Left", m_pad_data.accelerometer_x_g, accel_scale));
AddInput(new AccelerometerInput("Accel Right", m_pad_data.accelerometer_x_g, -accel_scale));
AddInput(new AccelerometerInput("Accel Forward", m_pad_data.accelerometer_z_g, accel_scale));
AddInput(new AccelerometerInput("Accel Backward", m_pad_data.accelerometer_z_g, -accel_scale));
// Convert degrees per second to radians per second
constexpr auto gyro_scale = 360.0 / MathUtil::TAU;
AddInput(new GyroInput("Gyro Pitch Up", m_pad_data.gyro_pitch_deg_s, gyro_scale));
AddInput(new GyroInput("Gyro Pitch Down", m_pad_data.gyro_pitch_deg_s, -gyro_scale));
AddInput(new GyroInput("Gyro Roll Left", m_pad_data.gyro_roll_deg_s, -gyro_scale));
AddInput(new GyroInput("Gyro Roll Right", m_pad_data.gyro_roll_deg_s, gyro_scale));
AddInput(new GyroInput("Gyro Yaw Left", m_pad_data.gyro_yaw_deg_s, -gyro_scale));
AddInput(new GyroInput("Gyro Yaw Right", m_pad_data.gyro_yaw_deg_s, gyro_scale));
AddInput(new BatteryInput(m_pad_data.battery_status));
m_touch_x_min = 0;
m_touch_y_min = 0;
// DS4 touchpad max values
m_touch_x_max = 1919;
m_touch_y_max = 941;
ResetPadData();
}
void Device::ResetPadData()
{
m_pad_data = Proto::MessageType::PadDataResponse{};
// Make sure they start from resting values, not from 0
m_touch_x = m_touch_x_min + ((m_touch_x_max - m_touch_x_min) / 2.0);
m_touch_y = m_touch_y_min + ((m_touch_y_max - m_touch_y_min) / 2.0);
m_pad_data.left_stick_x = 128;
m_pad_data.left_stick_y_inverted = 128;
m_pad_data.right_stick_x = 128;
m_pad_data.right_stick_y_inverted = 128;
m_pad_data.touch1.x = m_touch_x;
m_pad_data.touch1.y = m_touch_y;
}
std::string Device::GetName() const
{
return m_name;
}
std::string Device::GetSource() const
{
return std::string(DUALSHOCKUDP_SOURCE_NAME);
}
void Device::UpdateInput()
{
// Regularly tell the UDP server to feed us controller data
const auto now = SteadyClock::now();
if (now >= m_next_reregister)
{
m_next_reregister = now + SERVER_REREGISTER_INTERVAL;
Proto::Message<Proto::MessageType::PadDataRequest> msg(s_client_uid);
auto& data_req = msg.m_message;
data_req.register_flags = Proto::RegisterFlags::PadID;
data_req.pad_id_to_register = m_index;
msg.Finish();
if (m_socket.send(&data_req, sizeof(data_req), m_server_address, m_server_port) !=
sf::Socket::Status::Done)
{
ERROR_LOG_FMT(CONTROLLERINTERFACE, "DualShockUDPClient UpdateInput send failed");
}
}
// Receive and handle controller data
Proto::Message<Proto::MessageType::FromServer> msg;
std::size_t received_bytes;
sf::IpAddress sender;
u16 port;
while (m_socket.receive(&msg, sizeof msg, received_bytes, sender, port) ==
sf::Socket::Status::Done)
{
if (auto pad_data = msg.CheckAndCastTo<Proto::MessageType::PadDataResponse>())
{
m_pad_data = *pad_data;
// Update touch pad relative coordinates
if (m_pad_data.touch1.id != m_prev_touch.id)
m_prev_touch_valid = false;
if (m_prev_touch_valid)
{
m_touch_x += m_pad_data.touch1.x - m_prev_touch.x;
m_touch_y += m_pad_data.touch1.y - m_prev_touch.y;
m_touch_x = std::clamp(m_touch_x, -TOUCH_X_AXIS_MAX, TOUCH_X_AXIS_MAX);
m_touch_y = std::clamp(m_touch_y, -TOUCH_Y_AXIS_MAX, TOUCH_Y_AXIS_MAX);
}
m_prev_touch = m_pad_data.touch1;
m_prev_touch_valid = true;
}
}
}
std::optional<int> Device::GetPreferredId() const
{
return m_index;
}
} // namespace ciface::DualShockUDPClient
|