1/*
2 Simple DirectMedia Layer
3 Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
4
5 This software is provided 'as-is', without any express or implied
6 warranty. In no event will the authors be held liable for any damages
7 arising from the use of this software.
8
9 Permission is granted to anyone to use this software for any purpose,
10 including commercial applications, and to alter it and redistribute it
11 freely, subject to the following restrictions:
12
13 1. The origin of this software must not be misrepresented; you must not
14 claim that you wrote the original software. If you use this software
15 in a product, an acknowledgment in the product documentation would be
16 appreciated but is not required.
17 2. Altered source versions must be plainly marked as such, and must not be
18 misrepresented as being the original software.
19 3. This notice may not be removed or altered from any source distribution.
20*/
21#include "SDL_internal.h"
22
23#ifdef SDL_JOYSTICK_HIDAPI
24
25#include "../../SDL_hints_c.h"
26#include "../SDL_sysjoystick.h"
27#include "SDL_hidapijoystick_c.h"
28#include "SDL_hidapi_rumble.h"
29
30#ifdef SDL_JOYSTICK_HIDAPI_PS5
31
32// Define this if you want to log all packets from the controller
33#if 0
34#define DEBUG_PS5_PROTOCOL
35#endif
36
37// Define this if you want to log calibration data
38#if 0
39#define DEBUG_PS5_CALIBRATION
40#endif
41
42#define GYRO_RES_PER_DEGREE 1024.0f
43#define ACCEL_RES_PER_G 8192.0f
44#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
45
46#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))
47#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \
48 (((Uint32)(B)) << 8) | \
49 (((Uint32)(C)) << 16) | \
50 (((Uint32)(D)) << 24))
51
52enum
53{
54 SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11,
55 SDL_GAMEPAD_BUTTON_PS5_MICROPHONE,
56 SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION,
57 SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION,
58 SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE,
59 SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE
60};
61
62typedef enum
63{
64 k_EPS5ReportIdState = 0x01,
65 k_EPS5ReportIdUsbEffects = 0x02,
66 k_EPS5ReportIdBluetoothEffects = 0x31,
67 k_EPS5ReportIdBluetoothState = 0x31,
68} EPS5ReportId;
69
70typedef enum
71{
72 k_EPS5FeatureReportIdCapabilities = 0x03,
73 k_EPS5FeatureReportIdCalibration = 0x05,
74 k_EPS5FeatureReportIdSerialNumber = 0x09,
75 k_EPS5FeatureReportIdFirmwareInfo = 0x20,
76} EPS5FeatureReportId;
77
78typedef struct
79{
80 Uint8 ucLeftJoystickX;
81 Uint8 ucLeftJoystickY;
82 Uint8 ucRightJoystickX;
83 Uint8 ucRightJoystickY;
84 Uint8 rgucButtonsHatAndCounter[3];
85 Uint8 ucTriggerLeft;
86 Uint8 ucTriggerRight;
87} PS5SimpleStatePacket_t;
88
89typedef struct
90{
91 Uint8 ucLeftJoystickX; // 0
92 Uint8 ucLeftJoystickY; // 1
93 Uint8 ucRightJoystickX; // 2
94 Uint8 ucRightJoystickY; // 3
95 Uint8 ucTriggerLeft; // 4
96 Uint8 ucTriggerRight; // 5
97 Uint8 ucCounter; // 6
98 Uint8 rgucButtonsAndHat[4]; // 7
99 Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian
100 Uint8 rgucGyroX[2]; // 15
101 Uint8 rgucGyroY[2]; // 17
102 Uint8 rgucGyroZ[2]; // 19
103 Uint8 rgucAccelX[2]; // 21
104 Uint8 rgucAccelY[2]; // 23
105 Uint8 rgucAccelZ[2]; // 25
106 Uint8 rgucSensorTimestamp[4]; // 27 - 16/32 bit little endian
107
108} PS5StatePacketCommon_t;
109
110typedef struct
111{
112 Uint8 ucLeftJoystickX; // 0
113 Uint8 ucLeftJoystickY; // 1
114 Uint8 ucRightJoystickX; // 2
115 Uint8 ucRightJoystickY; // 3
116 Uint8 ucTriggerLeft; // 4
117 Uint8 ucTriggerRight; // 5
118 Uint8 ucCounter; // 6
119 Uint8 rgucButtonsAndHat[4]; // 7
120 Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian
121 Uint8 rgucGyroX[2]; // 15
122 Uint8 rgucGyroY[2]; // 17
123 Uint8 rgucGyroZ[2]; // 19
124 Uint8 rgucAccelX[2]; // 21
125 Uint8 rgucAccelY[2]; // 23
126 Uint8 rgucAccelZ[2]; // 25
127 Uint8 rgucSensorTimestamp[4]; // 27 - 32 bit little endian
128 Uint8 ucSensorTemp; // 31
129 Uint8 ucTouchpadCounter1; // 32 - high bit clear + counter
130 Uint8 rgucTouchpadData1[3]; // 33 - X/Y, 12 bits per axis
131 Uint8 ucTouchpadCounter2; // 36 - high bit clear + counter
132 Uint8 rgucTouchpadData2[3]; // 37 - X/Y, 12 bits per axis
133 Uint8 rgucUnknown1[8]; // 40
134 Uint8 rgucTimer2[4]; // 48 - 32 bit little endian
135 Uint8 ucBatteryLevel; // 52
136 Uint8 ucConnectState; // 53 - 0x08 = USB, 0x01 = headphone
137
138 // There's more unknown data at the end, and a 32-bit CRC on Bluetooth
139} PS5StatePacket_t;
140
141typedef struct
142{
143 Uint8 ucLeftJoystickX; // 0
144 Uint8 ucLeftJoystickY; // 1
145 Uint8 ucRightJoystickX; // 2
146 Uint8 ucRightJoystickY; // 3
147 Uint8 ucTriggerLeft; // 4
148 Uint8 ucTriggerRight; // 5
149 Uint8 ucCounter; // 6
150 Uint8 rgucButtonsAndHat[4]; // 7
151 Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian
152 Uint8 rgucGyroX[2]; // 15
153 Uint8 rgucGyroY[2]; // 17
154 Uint8 rgucGyroZ[2]; // 19
155 Uint8 rgucAccelX[2]; // 21
156 Uint8 rgucAccelY[2]; // 23
157 Uint8 rgucAccelZ[2]; // 25
158 Uint8 rgucSensorTimestamp[2]; // 27 - 16 bit little endian
159 Uint8 ucBatteryLevel; // 29
160 Uint8 ucUnknown; // 30
161 Uint8 ucTouchpadCounter1; // 31 - high bit clear + counter
162 Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis
163 Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter
164 Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis
165
166 // There's more unknown data at the end, and a 32-bit CRC on Bluetooth
167} PS5StatePacketAlt_t;
168
169typedef struct
170{
171 Uint8 ucEnableBits1; // 0
172 Uint8 ucEnableBits2; // 1
173 Uint8 ucRumbleRight; // 2
174 Uint8 ucRumbleLeft; // 3
175 Uint8 ucHeadphoneVolume; // 4
176 Uint8 ucSpeakerVolume; // 5
177 Uint8 ucMicrophoneVolume; // 6
178 Uint8 ucAudioEnableBits; // 7
179 Uint8 ucMicLightMode; // 8
180 Uint8 ucAudioMuteBits; // 9
181 Uint8 rgucRightTriggerEffect[11]; // 10
182 Uint8 rgucLeftTriggerEffect[11]; // 21
183 Uint8 rgucUnknown1[6]; // 32
184 Uint8 ucEnableBits3; // 38
185 Uint8 rgucUnknown2[2]; // 39
186 Uint8 ucLedAnim; // 41
187 Uint8 ucLedBrightness; // 42
188 Uint8 ucPadLights; // 43
189 Uint8 ucLedRed; // 44
190 Uint8 ucLedGreen; // 45
191 Uint8 ucLedBlue; // 46
192} DS5EffectsState_t;
193
194typedef enum
195{
196 k_EDS5EffectRumbleStart = (1 << 0),
197 k_EDS5EffectRumble = (1 << 1),
198 k_EDS5EffectLEDReset = (1 << 2),
199 k_EDS5EffectLED = (1 << 3),
200 k_EDS5EffectPadLights = (1 << 4),
201 k_EDS5EffectMicLight = (1 << 5)
202} EDS5Effect;
203
204typedef enum
205{
206 k_EDS5LEDResetStateNone,
207 k_EDS5LEDResetStatePending,
208 k_EDS5LEDResetStateComplete,
209} EDS5LEDResetState;
210
211typedef struct
212{
213 Sint16 bias;
214 float sensitivity;
215} IMUCalibrationData;
216
217/* Rumble hint mode:
218 * "0": enhanced features are never used
219 * "1": enhanced features are always used
220 * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
221 */
222typedef enum
223{
224 PS5_ENHANCED_REPORT_HINT_OFF,
225 PS5_ENHANCED_REPORT_HINT_ON,
226 PS5_ENHANCED_REPORT_HINT_AUTO
227} HIDAPI_PS5_EnhancedReportHint;
228
229typedef struct
230{
231 SDL_HIDAPI_Device *device;
232 SDL_Joystick *joystick;
233 bool is_nacon_dongle;
234 bool use_alternate_report;
235 bool sensors_supported;
236 bool lightbar_supported;
237 bool vibration_supported;
238 bool playerled_supported;
239 bool touchpad_supported;
240 bool effects_supported;
241 HIDAPI_PS5_EnhancedReportHint enhanced_report_hint;
242 bool enhanced_reports;
243 bool enhanced_mode;
244 bool enhanced_mode_available;
245 bool report_sensors;
246 bool report_touchpad;
247 bool report_battery;
248 bool hardware_calibration;
249 IMUCalibrationData calibration[6];
250 Uint16 firmware_version;
251 Uint64 last_packet;
252 int player_index;
253 bool player_lights;
254 Uint8 rumble_left;
255 Uint8 rumble_right;
256 bool color_set;
257 Uint8 led_red;
258 Uint8 led_green;
259 Uint8 led_blue;
260 EDS5LEDResetState led_reset_state;
261 Uint64 sensor_ticks;
262 Uint32 last_tick;
263 union
264 {
265 PS5SimpleStatePacket_t simple;
266 PS5StatePacketCommon_t state;
267 PS5StatePacketAlt_t alt_state;
268 PS5StatePacket_t full_state;
269 Uint8 data[64];
270 } last_state;
271} SDL_DriverPS5_Context;
272
273static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage);
274
275static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata)
276{
277 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);
278}
279
280static void HIDAPI_DriverPS5_UnregisterHints(SDL_HintCallback callback, void *userdata)
281{
282 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);
283}
284
285static bool HIDAPI_DriverPS5_IsEnabled(void)
286{
287 return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));
288}
289
290static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)
291{
292 SDL_memset(report, 0, length);
293 report[0] = report_id;
294 return SDL_hid_get_feature_report(dev, report, length);
295}
296
297static bool HIDAPI_DriverPS5_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
298{
299 Uint8 data[USB_PACKET_LENGTH];
300 int size;
301
302 if (type == SDL_GAMEPAD_TYPE_PS5) {
303 return true;
304 }
305
306 if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {
307 if (device && device->dev) {
308 size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));
309 if (size == 48 && data[2] == 0x28) {
310 // Supported third party controller
311 return true;
312 } else {
313 return false;
314 }
315 } else {
316 // Might be supported by this driver, enumerate and find out
317 return true;
318 }
319 }
320 return false;
321}
322
323static void SetLedsForPlayerIndex(DS5EffectsState_t *effects, int player_index)
324{
325 /* This list is the same as what hid-sony.c uses in the Linux kernel.
326 The first 4 values correspond to what the PS4 assigns.
327 */
328 static const Uint8 colors[7][3] = {
329 { 0x00, 0x00, 0x40 }, // Blue
330 { 0x40, 0x00, 0x00 }, // Red
331 { 0x00, 0x40, 0x00 }, // Green
332 { 0x20, 0x00, 0x20 }, // Pink
333 { 0x20, 0x10, 0x00 }, // Orange
334 { 0x00, 0x10, 0x10 }, // Teal
335 { 0x10, 0x10, 0x10 } // White
336 };
337
338 if (player_index >= 0) {
339 player_index %= SDL_arraysize(colors);
340 } else {
341 player_index = 0;
342 }
343
344 effects->ucLedRed = colors[player_index][0];
345 effects->ucLedGreen = colors[player_index][1];
346 effects->ucLedBlue = colors[player_index][2];
347}
348
349static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index)
350{
351 static const Uint8 lights[] = {
352 0x04,
353 0x0A,
354 0x15,
355 0x1B,
356 0x1F
357 };
358
359 if (player_index >= 0) {
360 // Bitmask, 0x1F enables all lights, 0x20 changes instantly instead of fade
361 player_index %= SDL_arraysize(lights);
362 effects->ucPadLights = lights[player_index] | 0x20;
363 } else {
364 effects->ucPadLights = 0x00;
365 }
366}
367
368static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
369{
370 SDL_DriverPS5_Context *ctx;
371 Uint8 data[USB_PACKET_LENGTH * 2];
372 int size;
373 char serial[18];
374 SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
375
376 ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx));
377 if (!ctx) {
378 return false;
379 }
380 ctx->device = device;
381
382 device->context = ctx;
383
384 if (device->serial && SDL_strlen(device->serial) == 12) {
385 int i, j;
386
387 j = -1;
388 for (i = 0; i < 12; i += 2) {
389 j += 1;
390 SDL_memmove(&serial[j], &device->serial[i], 2);
391 j += 2;
392 serial[j] = '-';
393 }
394 serial[j] = '\0';
395 } else {
396 serial[0] = '\0';
397 }
398
399 // Read a report to see what mode we're in
400 size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);
401#ifdef DEBUG_PS5_PROTOCOL
402 if (size > 0) {
403 HIDAPI_DumpPacket("PS5 first packet: size = %d", data, size);
404 } else {
405 SDL_Log("PS5 first packet: size = %d", size);
406 }
407#endif
408 if (size == 64) {
409 // Connected over USB
410 ctx->enhanced_reports = true;
411 } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) {
412 // Connected over Bluetooth, using enhanced reports
413 ctx->enhanced_reports = true;
414 } else {
415 // Connected over Bluetooth, using simple reports (DirectInput enabled)
416 }
417
418 if (device->vendor_id == USB_VENDOR_SONY && ctx->enhanced_reports) {
419 /* Read the serial number (Bluetooth address in reverse byte order)
420 This will also enable enhanced reports over Bluetooth
421 */
422 if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber, data, sizeof(data)) >= 7) {
423 (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
424 data[6], data[5], data[4], data[3], data[2], data[1]);
425 }
426
427 /* Read the firmware version
428 This will also enable enhanced reports over Bluetooth
429 */
430 if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdFirmwareInfo, data, USB_PACKET_LENGTH) >= 46) {
431 ctx->firmware_version = (Uint16)data[44] | ((Uint16)data[45] << 8);
432 }
433 }
434
435 // Get the device capabilities
436 if (device->vendor_id == USB_VENDOR_SONY) {
437 ctx->sensors_supported = true;
438 ctx->lightbar_supported = true;
439 ctx->vibration_supported = true;
440 ctx->playerled_supported = true;
441 ctx->touchpad_supported = true;
442 } else {
443 // Third party controller capability request
444 size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));
445 if (size == 48 && data[2] == 0x28) {
446 Uint8 capabilities = data[4];
447 Uint8 capabilities2 = data[20];
448 Uint8 device_type = data[5];
449
450#ifdef DEBUG_PS5_PROTOCOL
451 HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size);
452#endif
453 if (capabilities & 0x02) {
454 ctx->sensors_supported = true;
455 }
456 if (capabilities & 0x04) {
457 ctx->lightbar_supported = true;
458 }
459 if (capabilities & 0x08) {
460 ctx->vibration_supported = true;
461 }
462 if (capabilities & 0x40) {
463 ctx->touchpad_supported = true;
464 }
465 if (capabilities2 & 0x80) {
466 ctx->playerled_supported = true;
467 }
468
469 switch (device_type) {
470 case 0x00:
471 joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
472 break;
473 case 0x01:
474 joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
475 break;
476 case 0x02:
477 joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
478 break;
479 case 0x06:
480 joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
481 break;
482 case 0x07:
483 joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
484 break;
485 case 0x08:
486 joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
487 break;
488 default:
489 joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;
490 break;
491 }
492
493 ctx->use_alternate_report = true;
494
495 if (device->vendor_id == USB_VENDOR_NACON_ALT &&
496 (device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED ||
497 device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) {
498 // This doesn't report vibration capability, but it can do rumble
499 ctx->vibration_supported = true;
500 }
501 } else if (device->vendor_id == USB_VENDOR_RAZER &&
502 (device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED ||
503 device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) {
504 // The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration
505 ctx->sensors_supported = true;
506 ctx->touchpad_supported = true;
507 ctx->use_alternate_report = true;
508 } else if (device->vendor_id == USB_VENDOR_RAZER &&
509 device->product_id == USB_PRODUCT_RAZER_KITSUNE) {
510 // The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad
511 joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
512 ctx->touchpad_supported = true;
513 ctx->use_alternate_report = true;
514 }
515 }
516 ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported);
517
518 if (device->vendor_id == USB_VENDOR_NACON_ALT &&
519 device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) {
520 ctx->is_nacon_dongle = true;
521 }
522
523 device->joystick_type = joystick_type;
524 device->type = SDL_GAMEPAD_TYPE_PS5;
525 if (device->vendor_id == USB_VENDOR_SONY) {
526 if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {
527 HIDAPI_SetDeviceName(device, "DualSense Edge Wireless Controller");
528 } else {
529 HIDAPI_SetDeviceName(device, "DualSense Wireless Controller");
530 }
531 }
532 HIDAPI_SetDeviceSerial(device, serial);
533
534 if (ctx->is_nacon_dongle) {
535 // We don't know if this is connected yet, wait for reports
536 return true;
537 }
538
539 // Prefer the USB device over the Bluetooth device
540 if (device->is_bluetooth) {
541 if (HIDAPI_HasConnectedUSBDevice(device->serial)) {
542 return true;
543 }
544 } else {
545 HIDAPI_DisconnectBluetoothDevice(device->serial);
546 }
547 return HIDAPI_JoystickConnected(device, NULL);
548}
549
550static int HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)
551{
552 return -1;
553}
554
555static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device)
556{
557 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
558 int i, size;
559 Uint8 data[USB_PACKET_LENGTH];
560
561 size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCalibration, data, sizeof(data));
562 if (size < 35) {
563#ifdef DEBUG_PS5_CALIBRATION
564 SDL_Log("Short read of calibration data: %d, ignoring calibration", size);
565#endif
566 return;
567 }
568
569 {
570 Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;
571 Sint16 sGyroPitchPlus, sGyroPitchMinus;
572 Sint16 sGyroYawPlus, sGyroYawMinus;
573 Sint16 sGyroRollPlus, sGyroRollMinus;
574 Sint16 sGyroSpeedPlus, sGyroSpeedMinus;
575
576 Sint16 sAccXPlus, sAccXMinus;
577 Sint16 sAccYPlus, sAccYMinus;
578 Sint16 sAccZPlus, sAccZMinus;
579
580 float flNumerator;
581 Sint16 sRange2g;
582
583#ifdef DEBUG_PS5_CALIBRATION
584 HIDAPI_DumpPacket("PS5 calibration packet: size = %d", data, size);
585#endif
586
587 sGyroPitchBias = LOAD16(data[1], data[2]);
588 sGyroYawBias = LOAD16(data[3], data[4]);
589 sGyroRollBias = LOAD16(data[5], data[6]);
590
591 sGyroPitchPlus = LOAD16(data[7], data[8]);
592 sGyroPitchMinus = LOAD16(data[9], data[10]);
593 sGyroYawPlus = LOAD16(data[11], data[12]);
594 sGyroYawMinus = LOAD16(data[13], data[14]);
595 sGyroRollPlus = LOAD16(data[15], data[16]);
596 sGyroRollMinus = LOAD16(data[17], data[18]);
597
598 sGyroSpeedPlus = LOAD16(data[19], data[20]);
599 sGyroSpeedMinus = LOAD16(data[21], data[22]);
600
601 sAccXPlus = LOAD16(data[23], data[24]);
602 sAccXMinus = LOAD16(data[25], data[26]);
603 sAccYPlus = LOAD16(data[27], data[28]);
604 sAccYMinus = LOAD16(data[29], data[30]);
605 sAccZPlus = LOAD16(data[31], data[32]);
606 sAccZMinus = LOAD16(data[33], data[34]);
607
608 flNumerator = (sGyroSpeedPlus + sGyroSpeedMinus) * GYRO_RES_PER_DEGREE;
609 ctx->calibration[0].bias = sGyroPitchBias;
610 ctx->calibration[0].sensitivity = flNumerator / (sGyroPitchPlus - sGyroPitchMinus);
611
612 ctx->calibration[1].bias = sGyroYawBias;
613 ctx->calibration[1].sensitivity = flNumerator / (sGyroYawPlus - sGyroYawMinus);
614
615 ctx->calibration[2].bias = sGyroRollBias;
616 ctx->calibration[2].sensitivity = flNumerator / (sGyroRollPlus - sGyroRollMinus);
617
618 sRange2g = sAccXPlus - sAccXMinus;
619 ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;
620 ctx->calibration[3].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;
621
622 sRange2g = sAccYPlus - sAccYMinus;
623 ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;
624 ctx->calibration[4].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;
625
626 sRange2g = sAccZPlus - sAccZMinus;
627 ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;
628 ctx->calibration[5].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;
629
630 ctx->hardware_calibration = true;
631 for (i = 0; i < 6; ++i) {
632 float divisor = (i < 3 ? 64.0f : 1.0f);
633#ifdef DEBUG_PS5_CALIBRATION
634 SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity);
635#endif
636 // Some controllers have a bad calibration
637 if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) {
638#ifdef DEBUG_PS5_CALIBRATION
639 SDL_Log("invalid calibration, ignoring");
640#endif
641 ctx->hardware_calibration = false;
642 }
643 }
644 }
645}
646
647static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, int index, Sint16 value)
648{
649 float result;
650
651 if (ctx->hardware_calibration) {
652 IMUCalibrationData *calibration = &ctx->calibration[index];
653
654 result = (value - calibration->bias) * calibration->sensitivity;
655 } else if (index < 3) {
656 result = value * 64.f;
657 } else {
658 result = value;
659 }
660
661 // Convert the raw data to the units expected by SDL
662 if (index < 3) {
663 result = (result / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;
664 } else {
665 result = (result / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;
666 }
667 return result;
668}
669
670static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, bool application_usage)
671{
672 DS5EffectsState_t effects;
673
674 // Make sure the Bluetooth connection sequence has completed before sending LED color change
675 if (ctx->device->is_bluetooth && ctx->enhanced_reports &&
676 (effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) {
677 if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) {
678 ctx->led_reset_state = k_EDS5LEDResetStatePending;
679 return true;
680 }
681 }
682
683 SDL_zero(effects);
684
685 if (ctx->vibration_supported) {
686 if (ctx->rumble_left || ctx->rumble_right) {
687 if (ctx->firmware_version < 0x0224) {
688 effects.ucEnableBits1 |= 0x01; // Enable rumble emulation
689
690 // Shift to reduce effective rumble strength to match Xbox controllers
691 effects.ucRumbleLeft = ctx->rumble_left >> 1;
692 effects.ucRumbleRight = ctx->rumble_right >> 1;
693 } else {
694 effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer
695
696 effects.ucRumbleLeft = ctx->rumble_left;
697 effects.ucRumbleRight = ctx->rumble_right;
698 }
699 effects.ucEnableBits1 |= 0x02; // Disable audio haptics
700 } else {
701 // Leaving emulated rumble bits off will restore audio haptics
702 }
703
704 if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {
705 effects.ucEnableBits1 |= 0x02; // Disable audio haptics
706 }
707 if ((effect_mask & k_EDS5EffectRumble) != 0) {
708 // Already handled above
709 }
710 }
711 if (ctx->lightbar_supported) {
712 if ((effect_mask & k_EDS5EffectLEDReset) != 0) {
713 effects.ucEnableBits2 |= 0x08; // Reset LED state
714 }
715 if ((effect_mask & k_EDS5EffectLED) != 0) {
716 effects.ucEnableBits2 |= 0x04; // Enable LED color
717
718 // Populate the LED state with the appropriate color from our lookup table
719 if (ctx->color_set) {
720 effects.ucLedRed = ctx->led_red;
721 effects.ucLedGreen = ctx->led_green;
722 effects.ucLedBlue = ctx->led_blue;
723 } else {
724 SetLedsForPlayerIndex(&effects, ctx->player_index);
725 }
726 }
727 }
728 if (ctx->playerled_supported) {
729 if ((effect_mask & k_EDS5EffectPadLights) != 0) {
730 effects.ucEnableBits2 |= 0x10; // Enable touchpad lights
731
732 if (ctx->player_lights) {
733 SetLightsForPlayerIndex(&effects, ctx->player_index);
734 } else {
735 effects.ucPadLights = 0x00;
736 }
737 }
738 }
739 if ((effect_mask & k_EDS5EffectMicLight) != 0) {
740 effects.ucEnableBits2 |= 0x01; // Enable microphone light
741
742 effects.ucMicLightMode = 0; // Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse
743 }
744
745 return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
746}
747
748static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx)
749{
750 bool led_reset_complete = false;
751
752 if (ctx->enhanced_reports && ctx->sensors_supported && !ctx->use_alternate_report) {
753 const PS5StatePacketCommon_t *packet = &ctx->last_state.state;
754
755 // Check the timer to make sure the Bluetooth connection LED animation is complete
756 const Uint32 connection_complete = 10200000;
757 Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0],
758 packet->rgucSensorTimestamp[1],
759 packet->rgucSensorTimestamp[2],
760 packet->rgucSensorTimestamp[3]);
761 if (timestamp >= connection_complete) {
762 led_reset_complete = true;
763 }
764 } else {
765 // We don't know how to check the timer, just assume it's complete for now
766 led_reset_complete = true;
767 }
768
769 if (led_reset_complete) {
770 HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, false);
771
772 ctx->led_reset_state = k_EDS5LEDResetStateComplete;
773
774 HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);
775 }
776}
777
778static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)
779{
780 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
781
782 if (ctx->enhanced_reports) {
783 // This is just a dummy packet that should have no effect, since we don't set the CRC
784 Uint8 data[78];
785
786 SDL_zeroa(data);
787
788 data[0] = k_EPS5ReportIdBluetoothEffects;
789 data[1] = 0x02; // Magic value
790
791 if (SDL_HIDAPI_LockRumble()) {
792 SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));
793 }
794 } else {
795 // We can't even send an invalid effects packet, or it will put the controller in enhanced mode
796 if (device->num_joysticks > 0) {
797 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
798 }
799 }
800}
801
802static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx)
803{
804 if (ctx->enhanced_mode_available) {
805 return;
806 }
807 ctx->enhanced_mode_available = true;
808
809 if (ctx->touchpad_supported) {
810 SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);
811 ctx->report_touchpad = true;
812 }
813
814 if (ctx->sensors_supported) {
815 if (ctx->device->is_bluetooth) {
816 // Bluetooth sensor update rate appears to be 1000 Hz
817 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f);
818 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f);
819 } else {
820 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f);
821 SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f);
822 }
823 }
824
825 ctx->report_battery = true;
826
827 HIDAPI_UpdateDeviceProperties(ctx->device);
828}
829
830static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)
831{
832 HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
833
834 if (!ctx->enhanced_mode) {
835 ctx->enhanced_mode = true;
836
837 // Switch into enhanced report mode
838 HIDAPI_DriverPS5_UpdateEffects(ctx, 0, false);
839
840 // Update the light effects
841 HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);
842 }
843}
844
845static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint)
846{
847 switch (enhanced_report_hint) {
848 case PS5_ENHANCED_REPORT_HINT_OFF:
849 // Nothing to do, enhanced mode is a one-way ticket
850 break;
851 case PS5_ENHANCED_REPORT_HINT_ON:
852 HIDAPI_DriverPS5_SetEnhancedMode(ctx);
853 break;
854 case PS5_ENHANCED_REPORT_HINT_AUTO:
855 HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
856 break;
857 }
858 ctx->enhanced_report_hint = enhanced_report_hint;
859}
860
861static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx)
862{
863 ctx->enhanced_reports = true;
864
865 if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {
866 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
867 }
868}
869
870static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx)
871{
872 if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {
873 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
874 }
875}
876
877static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
878{
879 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
880
881 if (ctx->device->is_bluetooth) {
882 if (hint && SDL_strcasecmp(hint, "auto") == 0) {
883 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO);
884 } else if (SDL_GetStringBoolean(hint, true)) {
885 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
886 } else {
887 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF);
888 }
889 } else {
890 HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
891 }
892}
893
894static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
895{
896 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
897 bool player_lights = SDL_GetStringBoolean(hint, true);
898
899 if (player_lights != ctx->player_lights) {
900 ctx->player_lights = player_lights;
901
902 HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, false);
903 }
904}
905
906static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
907{
908 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
909
910 if (!ctx->joystick) {
911 return;
912 }
913
914 ctx->player_index = player_index;
915
916 // This will set the new LED state based on the new player index
917 // SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode
918 HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);
919}
920
921static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
922{
923 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
924
925 SDL_AssertJoysticksLocked();
926
927 ctx->joystick = joystick;
928 ctx->last_packet = SDL_GetTicks();
929 ctx->report_sensors = false;
930 ctx->report_touchpad = false;
931 ctx->rumble_left = 0;
932 ctx->rumble_right = 0;
933 ctx->color_set = false;
934 ctx->led_reset_state = k_EDS5LEDResetStateNone;
935 SDL_zero(ctx->last_state);
936
937 // Initialize player index (needed for setting LEDs)
938 ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);
939 ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, true);
940
941 // Initialize the joystick capabilities
942 if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {
943 joystick->nbuttons = 17; // paddles and touchpad and microphone
944 } else if (ctx->touchpad_supported) {
945 joystick->nbuttons = 13; // touchpad and microphone
946 } else {
947 joystick->nbuttons = 11;
948 }
949 joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
950 joystick->nhats = 1;
951 joystick->firmware_version = ctx->firmware_version;
952
953 SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
954 SDL_PS5EnhancedReportsChanged, ctx);
955 SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
956 SDL_PS5PlayerLEDHintChanged, ctx);
957
958 return true;
959}
960
961static bool HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
962{
963 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
964
965 if (!ctx->vibration_supported) {
966 return SDL_Unsupported();
967 }
968
969 if (!ctx->rumble_left && !ctx->rumble_right) {
970 HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, true);
971 }
972
973 ctx->rumble_left = (low_frequency_rumble >> 8);
974 ctx->rumble_right = (high_frequency_rumble >> 8);
975
976 return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, true);
977}
978
979static bool HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
980{
981 return SDL_Unsupported();
982}
983
984static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
985{
986 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
987 Uint32 result = 0;
988
989 if (ctx->enhanced_mode_available) {
990 if (ctx->lightbar_supported) {
991 result |= SDL_JOYSTICK_CAP_RGB_LED;
992 }
993 if (ctx->playerled_supported) {
994 result |= SDL_JOYSTICK_CAP_PLAYER_LED;
995 }
996 if (ctx->vibration_supported) {
997 result |= SDL_JOYSTICK_CAP_RUMBLE;
998 }
999 }
1000
1001 return result;
1002}
1003
1004static bool HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
1005{
1006 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1007
1008 if (!ctx->lightbar_supported) {
1009 return SDL_Unsupported();
1010 }
1011
1012 ctx->color_set = true;
1013 ctx->led_red = red;
1014 ctx->led_green = green;
1015 ctx->led_blue = blue;
1016
1017 return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, true);
1018}
1019
1020static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage)
1021{
1022 Uint8 data[78];
1023 int report_size, offset;
1024 Uint8 *pending_data;
1025 int *pending_size;
1026 int maximum_size;
1027
1028 if (!ctx->effects_supported) {
1029 // We shouldn't be sending packets to this controller
1030 return SDL_Unsupported();
1031 }
1032
1033 if (!ctx->enhanced_mode) {
1034 if (application_usage) {
1035 HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
1036 }
1037
1038 if (!ctx->enhanced_mode) {
1039 // We're not in enhanced mode, effects aren't allowed
1040 return SDL_Unsupported();
1041 }
1042 }
1043
1044 SDL_zeroa(data);
1045
1046 if (ctx->device->is_bluetooth) {
1047 data[0] = k_EPS5ReportIdBluetoothEffects;
1048 data[1] = 0x02; // Magic value
1049
1050 report_size = 78;
1051 offset = 2;
1052 } else {
1053 data[0] = k_EPS5ReportIdUsbEffects;
1054
1055 report_size = 48;
1056 offset = 1;
1057 }
1058
1059 SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
1060
1061 if (ctx->device->is_bluetooth) {
1062 // Bluetooth reports need a CRC at the end of the packet (at least on Linux)
1063 Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation
1064 Uint32 unCRC;
1065 unCRC = SDL_crc32(0, &ubHdr, 1);
1066 unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
1067 SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
1068 }
1069
1070 if (!SDL_HIDAPI_LockRumble()) {
1071 return false;
1072 }
1073
1074 // See if we can update an existing pending request
1075 if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) {
1076 DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset];
1077 DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];
1078 if (report_size == *pending_size &&
1079 effects->ucEnableBits1 == pending_effects->ucEnableBits1 &&
1080 effects->ucEnableBits2 == pending_effects->ucEnableBits2) {
1081 // We're simply updating the data for this request
1082 SDL_memcpy(pending_data, data, report_size);
1083 SDL_HIDAPI_UnlockRumble();
1084 return true;
1085 }
1086 }
1087
1088 if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) {
1089 return false;
1090 }
1091
1092 return true;
1093}
1094
1095static bool HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
1096{
1097 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1098
1099 return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, true);
1100}
1101
1102static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)
1103{
1104 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1105
1106 HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
1107
1108 if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {
1109 return SDL_Unsupported();
1110 }
1111
1112 if (enabled) {
1113 HIDAPI_DriverPS5_LoadCalibrationData(device);
1114 }
1115 ctx->report_sensors = enabled;
1116
1117 return true;
1118}
1119
1120static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5SimpleStatePacket_t *packet, Uint64 timestamp)
1121{
1122 Sint16 axis;
1123
1124 if (ctx->last_state.simple.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {
1125 {
1126 Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);
1127
1128 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));
1129 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));
1130 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));
1131 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));
1132 }
1133 {
1134 Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);
1135 Uint8 hat;
1136
1137 switch (data) {
1138 case 0:
1139 hat = SDL_HAT_UP;
1140 break;
1141 case 1:
1142 hat = SDL_HAT_RIGHTUP;
1143 break;
1144 case 2:
1145 hat = SDL_HAT_RIGHT;
1146 break;
1147 case 3:
1148 hat = SDL_HAT_RIGHTDOWN;
1149 break;
1150 case 4:
1151 hat = SDL_HAT_DOWN;
1152 break;
1153 case 5:
1154 hat = SDL_HAT_LEFTDOWN;
1155 break;
1156 case 6:
1157 hat = SDL_HAT_LEFT;
1158 break;
1159 case 7:
1160 hat = SDL_HAT_LEFTUP;
1161 break;
1162 default:
1163 hat = SDL_HAT_CENTERED;
1164 break;
1165 }
1166 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1167 }
1168 }
1169
1170 if (ctx->last_state.simple.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {
1171 Uint8 data = packet->rgucButtonsHatAndCounter[1];
1172
1173 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));
1174 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));
1175 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));
1176 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));
1177 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));
1178 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));
1179 }
1180
1181 if (ctx->last_state.simple.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {
1182 Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);
1183
1184 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));
1185 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));
1186 }
1187
1188 if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) {
1189 axis = SDL_JOYSTICK_AXIS_MAX;
1190 } else {
1191 axis = ((int)packet->ucTriggerLeft * 257) - 32768;
1192 }
1193 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1194 if (packet->ucTriggerRight == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x08)) {
1195 axis = SDL_JOYSTICK_AXIS_MAX;
1196 } else {
1197 axis = ((int)packet->ucTriggerRight * 257) - 32768;
1198 }
1199 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1200 axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
1201 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1202 axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
1203 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1204 axis = ((int)packet->ucRightJoystickX * 257) - 32768;
1205 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1206 axis = ((int)packet->ucRightJoystickY * 257) - 32768;
1207 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1208
1209 SDL_memcpy(&ctx->last_state.simple, packet, sizeof(ctx->last_state.simple));
1210}
1211
1212static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketCommon_t *packet, Uint64 timestamp)
1213{
1214 Sint16 axis;
1215
1216 if (ctx->last_state.state.rgucButtonsAndHat[0] != packet->rgucButtonsAndHat[0]) {
1217 {
1218 Uint8 data = (packet->rgucButtonsAndHat[0] >> 4);
1219
1220 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));
1221 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));
1222 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));
1223 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));
1224 }
1225 {
1226 Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F);
1227 Uint8 hat;
1228
1229 switch (data) {
1230 case 0:
1231 hat = SDL_HAT_UP;
1232 break;
1233 case 1:
1234 hat = SDL_HAT_RIGHTUP;
1235 break;
1236 case 2:
1237 hat = SDL_HAT_RIGHT;
1238 break;
1239 case 3:
1240 hat = SDL_HAT_RIGHTDOWN;
1241 break;
1242 case 4:
1243 hat = SDL_HAT_DOWN;
1244 break;
1245 case 5:
1246 hat = SDL_HAT_LEFTDOWN;
1247 break;
1248 case 6:
1249 hat = SDL_HAT_LEFT;
1250 break;
1251 case 7:
1252 hat = SDL_HAT_LEFTUP;
1253 break;
1254 default:
1255 hat = SDL_HAT_CENTERED;
1256 break;
1257 }
1258 SDL_SendJoystickHat(timestamp, joystick, 0, hat);
1259 }
1260 }
1261
1262 if (ctx->last_state.state.rgucButtonsAndHat[1] != packet->rgucButtonsAndHat[1]) {
1263 Uint8 data = packet->rgucButtonsAndHat[1];
1264
1265 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));
1266 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));
1267 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));
1268 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));
1269 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));
1270 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));
1271 }
1272
1273 if (ctx->last_state.state.rgucButtonsAndHat[2] != packet->rgucButtonsAndHat[2]) {
1274 Uint8 data = packet->rgucButtonsAndHat[2];
1275
1276 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));
1277 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));
1278 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, ((data & 0x04) != 0));
1279 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, ((data & 0x10) != 0));
1280 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, ((data & 0x20) != 0));
1281 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, ((data & 0x40) != 0));
1282 SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, ((data & 0x80) != 0));
1283 }
1284
1285 if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) {
1286 axis = SDL_JOYSTICK_AXIS_MAX;
1287 } else {
1288 axis = ((int)packet->ucTriggerLeft * 257) - 32768;
1289 }
1290 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
1291 if (packet->ucTriggerRight == 0 && (packet->rgucButtonsAndHat[1] & 0x08)) {
1292 axis = SDL_JOYSTICK_AXIS_MAX;
1293 } else {
1294 axis = ((int)packet->ucTriggerRight * 257) - 32768;
1295 }
1296 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
1297 axis = ((int)packet->ucLeftJoystickX * 257) - 32768;
1298 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
1299 axis = ((int)packet->ucLeftJoystickY * 257) - 32768;
1300 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
1301 axis = ((int)packet->ucRightJoystickX * 257) - 32768;
1302 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
1303 axis = ((int)packet->ucRightJoystickY * 257) - 32768;
1304 SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
1305
1306 if (ctx->report_sensors) {
1307 Uint64 sensor_timestamp;
1308 float data[3];
1309
1310 if (ctx->use_alternate_report) {
1311 // 16-bit timestamp
1312 Uint32 delta;
1313 Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0],
1314 packet->rgucSensorTimestamp[1]);
1315 if (ctx->last_tick < tick) {
1316 delta = (tick - ctx->last_tick);
1317 } else {
1318 delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);
1319 }
1320 ctx->last_tick = tick;
1321 ctx->sensor_ticks += delta;
1322
1323 // Sensor timestamp is in 1us units
1324 sensor_timestamp = SDL_US_TO_NS(ctx->sensor_ticks);
1325 } else {
1326 // 32-bit timestamp
1327 Uint32 delta;
1328 Uint32 tick = LOAD32(packet->rgucSensorTimestamp[0],
1329 packet->rgucSensorTimestamp[1],
1330 packet->rgucSensorTimestamp[2],
1331 packet->rgucSensorTimestamp[3]);
1332 if (ctx->last_tick < tick) {
1333 delta = (tick - ctx->last_tick);
1334 } else {
1335 delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1);
1336 }
1337 ctx->last_tick = tick;
1338 ctx->sensor_ticks += delta;
1339
1340 // Sensor timestamp is in 0.33us units
1341 sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US) / 3;
1342 }
1343
1344 data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));
1345 data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));
1346 data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));
1347 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);
1348
1349 data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));
1350 data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));
1351 data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));
1352 SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);
1353 }
1354}
1355
1356static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp)
1357{
1358 static const float TOUCHPAD_SCALEX = 1.0f / 1920;
1359 static const float TOUCHPAD_SCALEY = 1.0f / 1070;
1360 bool touchpad_down;
1361 int touchpad_x, touchpad_y;
1362
1363 if (ctx->report_touchpad) {
1364 touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);
1365 touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);
1366 touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);
1367 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1368
1369 touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);
1370 touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);
1371 touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);
1372 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1373 }
1374
1375 if (ctx->report_battery) {
1376 SDL_PowerState state;
1377 int percent;
1378 Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F;
1379 Uint8 level = (packet->ucBatteryLevel & 0x0F);
1380
1381 switch (status) {
1382 case 0:
1383 state = SDL_POWERSTATE_ON_BATTERY;
1384 percent = SDL_min(level * 10 + 5, 100);
1385 break;
1386 case 1:
1387 state = SDL_POWERSTATE_CHARGING;
1388 percent = SDL_min(level * 10 + 5, 100);
1389 break;
1390 case 2:
1391 state = SDL_POWERSTATE_CHARGED;
1392 percent = 100;
1393 break;
1394 default:
1395 state = SDL_POWERSTATE_UNKNOWN;
1396 percent = 0;
1397 break;
1398 }
1399 SDL_SendJoystickPowerInfo(joystick, state, percent);
1400 }
1401
1402 HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);
1403
1404 SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
1405}
1406
1407static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp)
1408{
1409 static const float TOUCHPAD_SCALEX = 1.0f / 1920;
1410 static const float TOUCHPAD_SCALEY = 1.0f / 1070;
1411 bool touchpad_down;
1412 int touchpad_x, touchpad_y;
1413
1414 if (ctx->report_touchpad) {
1415 touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);
1416 touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);
1417 touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);
1418 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1419
1420 touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);
1421 touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);
1422 touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);
1423 SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);
1424 }
1425
1426 HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);
1427
1428 SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));
1429}
1430
1431static bool VerifyCRC(Uint8 *data, int size)
1432{
1433 Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation
1434 Uint32 unCRC, unPacketCRC;
1435 Uint8 *packetCRC = data + size - sizeof(unPacketCRC);
1436 unCRC = SDL_crc32(0, &ubHdr, 1);
1437 unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));
1438
1439 unPacketCRC = LOAD32(packetCRC[0],
1440 packetCRC[1],
1441 packetCRC[2],
1442 packetCRC[3]);
1443 return (unCRC == unPacketCRC);
1444}
1445
1446static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size)
1447{
1448 switch (data[0]) {
1449 case k_EPS5ReportIdState:
1450 if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) {
1451 // The report timestamp doesn't change when the controller isn't connected
1452 PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1];
1453 if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) {
1454 return false;
1455 }
1456 if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 &&
1457 ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 &&
1458 ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) {
1459 // We don't have any state to compare yet, go ahead and copy it
1460 SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t));
1461 return false;
1462 }
1463 }
1464 return true;
1465
1466 case k_EPS5ReportIdBluetoothState:
1467 if (VerifyCRC(data, size)) {
1468 return true;
1469 }
1470 break;
1471 default:
1472 break;
1473 }
1474 return false;
1475}
1476
1477static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
1478{
1479 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1480 SDL_Joystick *joystick = NULL;
1481 Uint8 data[USB_PACKET_LENGTH * 2];
1482 int size;
1483 int packet_count = 0;
1484 Uint64 now = SDL_GetTicks();
1485
1486 if (device->num_joysticks > 0) {
1487 joystick = SDL_GetJoystickFromID(device->joysticks[0]);
1488 }
1489
1490 while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {
1491 Uint64 timestamp = SDL_GetTicksNS();
1492
1493#ifdef DEBUG_PS5_PROTOCOL
1494 HIDAPI_DumpPacket("PS5 packet: size = %d", data, size);
1495#endif
1496 if (!HIDAPI_DriverPS5_IsPacketValid(ctx, data, size)) {
1497 continue;
1498 }
1499
1500 ++packet_count;
1501 ctx->last_packet = now;
1502
1503 if (!joystick) {
1504 continue;
1505 }
1506
1507 switch (data[0]) {
1508 case k_EPS5ReportIdState:
1509 if (size == 10 || size == 78) {
1510 HIDAPI_DriverPS5_HandleSimpleStatePacket(joystick, device->dev, ctx, (PS5SimpleStatePacket_t *)&data[1], timestamp);
1511 } else {
1512 if (ctx->use_alternate_report) {
1513 HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[1], timestamp);
1514 } else {
1515 HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[1], timestamp);
1516 }
1517 }
1518 break;
1519 case k_EPS5ReportIdBluetoothState:
1520 // This is the extended report, we can enable effects now in auto mode
1521 HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx);
1522
1523 if (ctx->use_alternate_report) {
1524 HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2], timestamp);
1525 } else {
1526 HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2], timestamp);
1527 }
1528 if (ctx->led_reset_state == k_EDS5LEDResetStatePending) {
1529 HIDAPI_DriverPS5_CheckPendingLEDReset(ctx);
1530 }
1531 break;
1532 default:
1533#ifdef DEBUG_JOYSTICK
1534 SDL_Log("Unknown PS5 packet: 0x%.2x", data[0]);
1535#endif
1536 break;
1537 }
1538 }
1539
1540 if (device->is_bluetooth) {
1541 if (packet_count == 0) {
1542 // Check to see if it looks like the device disconnected
1543 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1544 // Send an empty output report to tickle the Bluetooth stack
1545 HIDAPI_DriverPS5_TickleBluetooth(device);
1546 ctx->last_packet = now;
1547 }
1548 } else {
1549 // Reconnect the Bluetooth device once the USB device is gone
1550 if (device->num_joysticks == 0 &&
1551 !HIDAPI_HasConnectedUSBDevice(device->serial)) {
1552 HIDAPI_JoystickConnected(device, NULL);
1553 }
1554 }
1555 }
1556
1557 if (ctx->is_nacon_dongle) {
1558 if (packet_count == 0) {
1559 if (device->num_joysticks > 0) {
1560 // Check to see if it looks like the device disconnected
1561 if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {
1562 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1563 }
1564 }
1565 } else {
1566 if (device->num_joysticks == 0) {
1567 HIDAPI_JoystickConnected(device, NULL);
1568 }
1569 }
1570 }
1571
1572 if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {
1573 // Read error, device is disconnected
1574 HIDAPI_JoystickDisconnected(device, device->joysticks[0]);
1575 }
1576 return (size >= 0);
1577}
1578
1579static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
1580{
1581 SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
1582
1583 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
1584 SDL_PS5EnhancedReportsChanged, ctx);
1585
1586 SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
1587 SDL_PS5PlayerLEDHintChanged, ctx);
1588
1589 ctx->joystick = NULL;
1590
1591 ctx->report_sensors = false;
1592 ctx->enhanced_mode = false;
1593 ctx->enhanced_mode_available = false;
1594}
1595
1596static void HIDAPI_DriverPS5_FreeDevice(SDL_HIDAPI_Device *device)
1597{
1598}
1599
1600SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = {
1601 SDL_HINT_JOYSTICK_HIDAPI_PS5,
1602 true,
1603 HIDAPI_DriverPS5_RegisterHints,
1604 HIDAPI_DriverPS5_UnregisterHints,
1605 HIDAPI_DriverPS5_IsEnabled,
1606 HIDAPI_DriverPS5_IsSupportedDevice,
1607 HIDAPI_DriverPS5_InitDevice,
1608 HIDAPI_DriverPS5_GetDevicePlayerIndex,
1609 HIDAPI_DriverPS5_SetDevicePlayerIndex,
1610 HIDAPI_DriverPS5_UpdateDevice,
1611 HIDAPI_DriverPS5_OpenJoystick,
1612 HIDAPI_DriverPS5_RumbleJoystick,
1613 HIDAPI_DriverPS5_RumbleJoystickTriggers,
1614 HIDAPI_DriverPS5_GetJoystickCapabilities,
1615 HIDAPI_DriverPS5_SetJoystickLED,
1616 HIDAPI_DriverPS5_SendJoystickEffect,
1617 HIDAPI_DriverPS5_SetJoystickSensorsEnabled,
1618 HIDAPI_DriverPS5_CloseJoystick,
1619 HIDAPI_DriverPS5_FreeDevice,
1620};
1621
1622#endif // SDL_JOYSTICK_HIDAPI_PS5
1623
1624#endif // SDL_JOYSTICK_HIDAPI
1625