Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_ps5.c
21798 views
/*1Simple DirectMedia Layer2Copyright (C) 1997-2025 Sam Lantinga <[email protected]>34This software is provided 'as-is', without any express or implied5warranty. In no event will the authors be held liable for any damages6arising from the use of this software.78Permission is granted to anyone to use this software for any purpose,9including commercial applications, and to alter it and redistribute it10freely, subject to the following restrictions:11121. The origin of this software must not be misrepresented; you must not13claim that you wrote the original software. If you use this software14in a product, an acknowledgment in the product documentation would be15appreciated but is not required.162. Altered source versions must be plainly marked as such, and must not be17misrepresented as being the original software.183. This notice may not be removed or altered from any source distribution.19*/20#include "SDL_internal.h"2122#ifdef SDL_JOYSTICK_HIDAPI2324#include "../../SDL_hints_c.h"25#include "../SDL_sysjoystick.h"26#include "SDL_hidapijoystick_c.h"27#include "SDL_hidapi_rumble.h"2829#ifdef SDL_JOYSTICK_HIDAPI_PS53031// Define this if you want to log all packets from the controller32#if 033#define DEBUG_PS5_PROTOCOL34#endif3536// Define this if you want to log calibration data37#if 038#define DEBUG_PS5_CALIBRATION39#endif4041#define GYRO_RES_PER_DEGREE 1024.0f42#define ACCEL_RES_PER_G 8192.0f43#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 5004445#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))46#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \47(((Uint32)(B)) << 8) | \48(((Uint32)(C)) << 16) | \49(((Uint32)(D)) << 24))5051enum52{53SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD = 11,54SDL_GAMEPAD_BUTTON_PS5_MICROPHONE,55SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION,56SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION,57SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE,58SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE59};6061typedef enum62{63k_EPS5ReportIdState = 0x01,64k_EPS5ReportIdUsbEffects = 0x02,65k_EPS5ReportIdBluetoothEffects = 0x31,66k_EPS5ReportIdBluetoothState = 0x31,67} EPS5ReportId;6869typedef enum70{71k_EPS5FeatureReportIdCapabilities = 0x03,72k_EPS5FeatureReportIdCalibration = 0x05,73k_EPS5FeatureReportIdSerialNumber = 0x09,74k_EPS5FeatureReportIdFirmwareInfo = 0x20,75} EPS5FeatureReportId;7677typedef struct78{79Uint8 ucLeftJoystickX;80Uint8 ucLeftJoystickY;81Uint8 ucRightJoystickX;82Uint8 ucRightJoystickY;83Uint8 rgucButtonsHatAndCounter[3];84Uint8 ucTriggerLeft;85Uint8 ucTriggerRight;86} PS5SimpleStatePacket_t;8788typedef struct89{90Uint8 ucLeftJoystickX; // 091Uint8 ucLeftJoystickY; // 192Uint8 ucRightJoystickX; // 293Uint8 ucRightJoystickY; // 394Uint8 ucTriggerLeft; // 495Uint8 ucTriggerRight; // 596Uint8 ucCounter; // 697Uint8 rgucButtonsAndHat[4]; // 798Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian99Uint8 rgucGyroX[2]; // 15100Uint8 rgucGyroY[2]; // 17101Uint8 rgucGyroZ[2]; // 19102Uint8 rgucAccelX[2]; // 21103Uint8 rgucAccelY[2]; // 23104Uint8 rgucAccelZ[2]; // 25105Uint8 rgucSensorTimestamp[4]; // 27 - 16/32 bit little endian106107} PS5StatePacketCommon_t;108109typedef struct110{111Uint8 ucLeftJoystickX; // 0112Uint8 ucLeftJoystickY; // 1113Uint8 ucRightJoystickX; // 2114Uint8 ucRightJoystickY; // 3115Uint8 ucTriggerLeft; // 4116Uint8 ucTriggerRight; // 5117Uint8 ucCounter; // 6118Uint8 rgucButtonsAndHat[4]; // 7119Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian120Uint8 rgucGyroX[2]; // 15121Uint8 rgucGyroY[2]; // 17122Uint8 rgucGyroZ[2]; // 19123Uint8 rgucAccelX[2]; // 21124Uint8 rgucAccelY[2]; // 23125Uint8 rgucAccelZ[2]; // 25126Uint8 rgucSensorTimestamp[4]; // 27 - 32 bit little endian127Uint8 ucSensorTemp; // 31128Uint8 ucTouchpadCounter1; // 32 - high bit clear + counter129Uint8 rgucTouchpadData1[3]; // 33 - X/Y, 12 bits per axis130Uint8 ucTouchpadCounter2; // 36 - high bit clear + counter131Uint8 rgucTouchpadData2[3]; // 37 - X/Y, 12 bits per axis132Uint8 rgucUnknown1[8]; // 40133Uint8 rgucTimer2[4]; // 48 - 32 bit little endian134Uint8 ucBatteryLevel; // 52135Uint8 ucConnectState; // 53 - 0x08 = USB, 0x01 = headphone136137// There's more unknown data at the end, and a 32-bit CRC on Bluetooth138} PS5StatePacket_t;139140typedef struct141{142Uint8 ucLeftJoystickX; // 0143Uint8 ucLeftJoystickY; // 1144Uint8 ucRightJoystickX; // 2145Uint8 ucRightJoystickY; // 3146Uint8 ucTriggerLeft; // 4147Uint8 ucTriggerRight; // 5148Uint8 ucCounter; // 6149Uint8 rgucButtonsAndHat[4]; // 7150Uint8 rgucPacketSequence[4]; // 11 - 32 bit little endian151Uint8 rgucGyroX[2]; // 15152Uint8 rgucGyroY[2]; // 17153Uint8 rgucGyroZ[2]; // 19154Uint8 rgucAccelX[2]; // 21155Uint8 rgucAccelY[2]; // 23156Uint8 rgucAccelZ[2]; // 25157Uint8 rgucSensorTimestamp[2]; // 27 - 16 bit little endian158Uint8 ucBatteryLevel; // 29159Uint8 ucUnknown; // 30160Uint8 ucTouchpadCounter1; // 31 - high bit clear + counter161Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis162Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter163Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis164165// There's more unknown data at the end, and a 32-bit CRC on Bluetooth166} PS5StatePacketAlt_t;167168typedef struct169{170Uint8 ucEnableBits1; // 0171Uint8 ucEnableBits2; // 1172Uint8 ucRumbleRight; // 2173Uint8 ucRumbleLeft; // 3174Uint8 ucHeadphoneVolume; // 4175Uint8 ucSpeakerVolume; // 5176Uint8 ucMicrophoneVolume; // 6177Uint8 ucAudioEnableBits; // 7178Uint8 ucMicLightMode; // 8179Uint8 ucAudioMuteBits; // 9180Uint8 rgucRightTriggerEffect[11]; // 10181Uint8 rgucLeftTriggerEffect[11]; // 21182Uint8 rgucUnknown1[6]; // 32183Uint8 ucEnableBits3; // 38184Uint8 rgucUnknown2[2]; // 39185Uint8 ucLedAnim; // 41186Uint8 ucLedBrightness; // 42187Uint8 ucPadLights; // 43188Uint8 ucLedRed; // 44189Uint8 ucLedGreen; // 45190Uint8 ucLedBlue; // 46191} DS5EffectsState_t;192193typedef enum194{195k_EDS5EffectRumbleStart = (1 << 0),196k_EDS5EffectRumble = (1 << 1),197k_EDS5EffectLEDReset = (1 << 2),198k_EDS5EffectLED = (1 << 3),199k_EDS5EffectPadLights = (1 << 4),200k_EDS5EffectMicLight = (1 << 5)201} EDS5Effect;202203typedef enum204{205k_EDS5LEDResetStateNone,206k_EDS5LEDResetStatePending,207k_EDS5LEDResetStateComplete,208} EDS5LEDResetState;209210typedef struct211{212Sint16 bias;213float sensitivity;214} IMUCalibrationData;215216/* Rumble hint mode:217* "0": enhanced features are never used218* "1": enhanced features are always used219* "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.220*/221typedef enum222{223PS5_ENHANCED_REPORT_HINT_OFF,224PS5_ENHANCED_REPORT_HINT_ON,225PS5_ENHANCED_REPORT_HINT_AUTO226} HIDAPI_PS5_EnhancedReportHint;227228typedef struct229{230SDL_HIDAPI_Device *device;231SDL_Joystick *joystick;232bool is_nacon_dongle;233bool use_alternate_report;234bool sensors_supported;235bool lightbar_supported;236bool vibration_supported;237bool playerled_supported;238bool touchpad_supported;239bool effects_supported;240HIDAPI_PS5_EnhancedReportHint enhanced_report_hint;241bool enhanced_reports;242bool enhanced_mode;243bool enhanced_mode_available;244bool report_sensors;245bool report_touchpad;246bool report_battery;247bool hardware_calibration;248IMUCalibrationData calibration[6];249Uint16 firmware_version;250Uint64 last_packet;251int player_index;252bool player_lights;253bool enhanced_rumble;254Uint8 rumble_left;255Uint8 rumble_right;256bool color_set;257Uint8 led_red;258Uint8 led_green;259Uint8 led_blue;260EDS5LEDResetState led_reset_state;261Uint64 sensor_ticks;262Uint32 last_tick;263union264{265PS5SimpleStatePacket_t simple;266PS5StatePacketCommon_t state;267PS5StatePacketAlt_t alt_state;268PS5StatePacket_t full_state;269Uint8 data[64];270} last_state;271} SDL_DriverPS5_Context;272273static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage);274275static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata)276{277SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);278}279280static void HIDAPI_DriverPS5_UnregisterHints(SDL_HintCallback callback, void *userdata)281{282SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);283}284285static bool HIDAPI_DriverPS5_IsEnabled(void)286{287return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));288}289290static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)291{292SDL_memset(report, 0, length);293report[0] = report_id;294return SDL_hid_get_feature_report(dev, report, length);295}296297static 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{299Uint8 data[USB_PACKET_LENGTH];300int size;301302if (type == SDL_GAMEPAD_TYPE_PS5) {303return true;304}305306if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {307if (device && device->dev) {308size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));309if (size == 48 && data[2] == 0x28) {310// Supported third party controller311return true;312} else {313return false;314}315} else {316// Might be supported by this driver, enumerate and find out317return true;318}319}320return false;321}322323static 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.326The first 4 values correspond to what the PS4 assigns.327*/328static const Uint8 colors[7][3] = {329{ 0x00, 0x00, 0x40 }, // Blue330{ 0x40, 0x00, 0x00 }, // Red331{ 0x00, 0x40, 0x00 }, // Green332{ 0x20, 0x00, 0x20 }, // Pink333{ 0x20, 0x10, 0x00 }, // Orange334{ 0x00, 0x10, 0x10 }, // Teal335{ 0x10, 0x10, 0x10 } // White336};337338if (player_index >= 0) {339player_index %= SDL_arraysize(colors);340} else {341player_index = 0;342}343344effects->ucLedRed = colors[player_index][0];345effects->ucLedGreen = colors[player_index][1];346effects->ucLedBlue = colors[player_index][2];347}348349static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index)350{351static const Uint8 lights[] = {3520x04,3530x0A,3540x15,3550x1B,3560x1F357};358359if (player_index >= 0) {360// Bitmask, 0x1F enables all lights, 0x20 changes instantly instead of fade361player_index %= SDL_arraysize(lights);362effects->ucPadLights = lights[player_index] | 0x20;363} else {364effects->ucPadLights = 0x00;365}366}367368static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)369{370SDL_DriverPS5_Context *ctx;371Uint8 data[USB_PACKET_LENGTH * 2];372int size;373char serial[18];374SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;375376ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx));377if (!ctx) {378return false;379}380ctx->device = device;381382device->context = ctx;383384if (device->serial && SDL_strlen(device->serial) == 12) {385int i, j;386387j = -1;388for (i = 0; i < 12; i += 2) {389j += 1;390SDL_memmove(&serial[j], &device->serial[i], 2);391j += 2;392serial[j] = '-';393}394serial[j] = '\0';395} else {396serial[0] = '\0';397}398399// Read a report to see what mode we're in400size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);401#ifdef DEBUG_PS5_PROTOCOL402if (size > 0) {403HIDAPI_DumpPacket("PS5 first packet: size = %d", data, size);404} else {405SDL_Log("PS5 first packet: size = %d", size);406}407#endif408if (size == 64) {409// Connected over USB410ctx->enhanced_reports = true;411} else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) {412// Connected over Bluetooth, using enhanced reports413ctx->enhanced_reports = true;414} else {415// Connected over Bluetooth, using simple reports (DirectInput enabled)416}417418if (device->vendor_id == USB_VENDOR_SONY && ctx->enhanced_reports) {419/* Read the serial number (Bluetooth address in reverse byte order)420This will also enable enhanced reports over Bluetooth421*/422if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber, data, sizeof(data)) >= 7) {423(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",424data[6], data[5], data[4], data[3], data[2], data[1]);425}426427/* Read the firmware version428This will also enable enhanced reports over Bluetooth429*/430if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdFirmwareInfo, data, USB_PACKET_LENGTH) >= 46) {431ctx->firmware_version = (Uint16)data[44] | ((Uint16)data[45] << 8);432}433}434435if (device->vendor_id == USB_VENDOR_SONY) {436if (device->product_id == USB_PRODUCT_SONY_DS5_EDGE ||437ctx->firmware_version == 0 || // Assume that it's updated firmware over Bluetooth438ctx->firmware_version >= 0x0224) {439ctx->enhanced_rumble = true;440}441}442443// Get the device capabilities444if (device->vendor_id == USB_VENDOR_SONY) {445ctx->sensors_supported = true;446ctx->lightbar_supported = true;447ctx->vibration_supported = true;448ctx->playerled_supported = true;449ctx->touchpad_supported = true;450} else {451// Third party controller capability request452size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));453if (size == 48 && data[2] == 0x28) {454Uint8 capabilities = data[4];455Uint8 capabilities2 = data[20];456Uint8 device_type = data[5];457458#ifdef DEBUG_PS5_PROTOCOL459HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size);460#endif461if (capabilities & 0x02) {462ctx->sensors_supported = true;463}464if (capabilities & 0x04) {465ctx->lightbar_supported = true;466}467if (capabilities & 0x08) {468ctx->vibration_supported = true;469}470if (capabilities & 0x40) {471ctx->touchpad_supported = true;472}473if (capabilities2 & 0x80) {474ctx->playerled_supported = true;475}476477switch (device_type) {478case 0x00:479joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;480break;481case 0x01:482joystick_type = SDL_JOYSTICK_TYPE_GUITAR;483break;484case 0x02:485joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;486break;487case 0x06:488joystick_type = SDL_JOYSTICK_TYPE_WHEEL;489break;490case 0x07:491joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;492break;493case 0x08:494joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;495break;496default:497joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;498break;499}500501ctx->use_alternate_report = true;502503if (device->vendor_id == USB_VENDOR_NACON_ALT &&504(device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED ||505device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) {506// This doesn't report vibration capability, but it can do rumble507ctx->vibration_supported = true;508}509} else if (device->vendor_id == USB_VENDOR_RAZER &&510(device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED ||511device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) {512// The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration513ctx->sensors_supported = true;514ctx->touchpad_supported = true;515ctx->use_alternate_report = true;516} else if (device->vendor_id == USB_VENDOR_RAZER &&517device->product_id == USB_PRODUCT_RAZER_KITSUNE) {518// The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad519joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;520ctx->touchpad_supported = true;521ctx->use_alternate_report = true;522}523}524ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported);525526if (device->vendor_id == USB_VENDOR_NACON_ALT &&527device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) {528ctx->is_nacon_dongle = true;529}530531device->joystick_type = joystick_type;532device->type = SDL_GAMEPAD_TYPE_PS5;533if (device->vendor_id == USB_VENDOR_SONY) {534if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {535HIDAPI_SetDeviceName(device, "DualSense Edge Wireless Controller");536} else {537HIDAPI_SetDeviceName(device, "DualSense Wireless Controller");538}539}540HIDAPI_SetDeviceSerial(device, serial);541542if (ctx->is_nacon_dongle) {543// We don't know if this is connected yet, wait for reports544return true;545}546547// Prefer the USB device over the Bluetooth device548if (device->is_bluetooth) {549if (HIDAPI_HasConnectedUSBDevice(device->serial)) {550return true;551}552} else {553HIDAPI_DisconnectBluetoothDevice(device->serial);554}555return HIDAPI_JoystickConnected(device, NULL);556}557558static int HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)559{560return -1;561}562563static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device)564{565SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;566int i, size;567Uint8 data[USB_PACKET_LENGTH];568569size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCalibration, data, sizeof(data));570if (size < 35) {571#ifdef DEBUG_PS5_CALIBRATION572SDL_Log("Short read of calibration data: %d, ignoring calibration", size);573#endif574return;575}576577{578Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;579Sint16 sGyroPitchPlus, sGyroPitchMinus;580Sint16 sGyroYawPlus, sGyroYawMinus;581Sint16 sGyroRollPlus, sGyroRollMinus;582Sint16 sGyroSpeedPlus, sGyroSpeedMinus;583584Sint16 sAccXPlus, sAccXMinus;585Sint16 sAccYPlus, sAccYMinus;586Sint16 sAccZPlus, sAccZMinus;587588float flNumerator;589Sint16 sRange2g;590591#ifdef DEBUG_PS5_CALIBRATION592HIDAPI_DumpPacket("PS5 calibration packet: size = %d", data, size);593#endif594595sGyroPitchBias = LOAD16(data[1], data[2]);596sGyroYawBias = LOAD16(data[3], data[4]);597sGyroRollBias = LOAD16(data[5], data[6]);598599sGyroPitchPlus = LOAD16(data[7], data[8]);600sGyroPitchMinus = LOAD16(data[9], data[10]);601sGyroYawPlus = LOAD16(data[11], data[12]);602sGyroYawMinus = LOAD16(data[13], data[14]);603sGyroRollPlus = LOAD16(data[15], data[16]);604sGyroRollMinus = LOAD16(data[17], data[18]);605606sGyroSpeedPlus = LOAD16(data[19], data[20]);607sGyroSpeedMinus = LOAD16(data[21], data[22]);608609sAccXPlus = LOAD16(data[23], data[24]);610sAccXMinus = LOAD16(data[25], data[26]);611sAccYPlus = LOAD16(data[27], data[28]);612sAccYMinus = LOAD16(data[29], data[30]);613sAccZPlus = LOAD16(data[31], data[32]);614sAccZMinus = LOAD16(data[33], data[34]);615616flNumerator = (sGyroSpeedPlus + sGyroSpeedMinus) * GYRO_RES_PER_DEGREE;617ctx->calibration[0].bias = sGyroPitchBias;618ctx->calibration[0].sensitivity = flNumerator / (sGyroPitchPlus - sGyroPitchMinus);619620ctx->calibration[1].bias = sGyroYawBias;621ctx->calibration[1].sensitivity = flNumerator / (sGyroYawPlus - sGyroYawMinus);622623ctx->calibration[2].bias = sGyroRollBias;624ctx->calibration[2].sensitivity = flNumerator / (sGyroRollPlus - sGyroRollMinus);625626sRange2g = sAccXPlus - sAccXMinus;627ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;628ctx->calibration[3].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;629630sRange2g = sAccYPlus - sAccYMinus;631ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;632ctx->calibration[4].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;633634sRange2g = sAccZPlus - sAccZMinus;635ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;636ctx->calibration[5].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;637638ctx->hardware_calibration = true;639for (i = 0; i < 6; ++i) {640float divisor = (i < 3 ? 64.0f : 1.0f);641#ifdef DEBUG_PS5_CALIBRATION642SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity);643#endif644// Some controllers have a bad calibration645if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) {646#ifdef DEBUG_PS5_CALIBRATION647SDL_Log("invalid calibration, ignoring");648#endif649ctx->hardware_calibration = false;650}651}652}653}654655static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, int index, Sint16 value)656{657float result;658659if (ctx->hardware_calibration) {660IMUCalibrationData *calibration = &ctx->calibration[index];661662result = (value - calibration->bias) * calibration->sensitivity;663} else if (index < 3) {664result = value * 64.f;665} else {666result = value;667}668669// Convert the raw data to the units expected by SDL670if (index < 3) {671result = (result / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;672} else {673result = (result / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;674}675return result;676}677678static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, bool application_usage)679{680DS5EffectsState_t effects;681682// Make sure the Bluetooth connection sequence has completed before sending LED color change683if (ctx->device->is_bluetooth && ctx->enhanced_reports &&684(effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) {685if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) {686ctx->led_reset_state = k_EDS5LEDResetStatePending;687return true;688}689}690691SDL_zero(effects);692693if (ctx->vibration_supported) {694if (ctx->rumble_left || ctx->rumble_right) {695if (ctx->enhanced_rumble) {696effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer697698effects.ucRumbleLeft = ctx->rumble_left;699effects.ucRumbleRight = ctx->rumble_right;700} else {701effects.ucEnableBits1 |= 0x01; // Enable rumble emulation702703// Shift to reduce effective rumble strength to match Xbox controllers704effects.ucRumbleLeft = ctx->rumble_left >> 1;705effects.ucRumbleRight = ctx->rumble_right >> 1;706}707effects.ucEnableBits1 |= 0x02; // Disable audio haptics708} else {709// Leaving emulated rumble bits off will restore audio haptics710}711712if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {713effects.ucEnableBits1 |= 0x02; // Disable audio haptics714}715if ((effect_mask & k_EDS5EffectRumble) != 0) {716// Already handled above717}718}719if (ctx->lightbar_supported) {720if ((effect_mask & k_EDS5EffectLEDReset) != 0) {721effects.ucEnableBits2 |= 0x08; // Reset LED state722}723if ((effect_mask & k_EDS5EffectLED) != 0) {724effects.ucEnableBits2 |= 0x04; // Enable LED color725726// Populate the LED state with the appropriate color from our lookup table727if (ctx->color_set) {728effects.ucLedRed = ctx->led_red;729effects.ucLedGreen = ctx->led_green;730effects.ucLedBlue = ctx->led_blue;731} else {732SetLedsForPlayerIndex(&effects, ctx->player_index);733}734}735}736if (ctx->playerled_supported) {737if ((effect_mask & k_EDS5EffectPadLights) != 0) {738effects.ucEnableBits2 |= 0x10; // Enable touchpad lights739740if (ctx->player_lights) {741SetLightsForPlayerIndex(&effects, ctx->player_index);742} else {743effects.ucPadLights = 0x00;744}745}746}747if ((effect_mask & k_EDS5EffectMicLight) != 0) {748effects.ucEnableBits2 |= 0x01; // Enable microphone light749750effects.ucMicLightMode = 0; // Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse751}752753return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);754}755756static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx)757{758bool led_reset_complete = false;759760if (ctx->enhanced_reports && ctx->sensors_supported && !ctx->use_alternate_report) {761const PS5StatePacketCommon_t *packet = &ctx->last_state.state;762763// Check the timer to make sure the Bluetooth connection LED animation is complete764const Uint32 connection_complete = 10200000;765Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0],766packet->rgucSensorTimestamp[1],767packet->rgucSensorTimestamp[2],768packet->rgucSensorTimestamp[3]);769if (timestamp >= connection_complete) {770led_reset_complete = true;771}772} else {773// We don't know how to check the timer, just assume it's complete for now774led_reset_complete = true;775}776777if (led_reset_complete) {778HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, false);779780ctx->led_reset_state = k_EDS5LEDResetStateComplete;781782HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);783}784}785786static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)787{788SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;789790if (ctx->enhanced_reports) {791// This is just a dummy packet that should have no effect, since we don't set the CRC792Uint8 data[78];793794SDL_zeroa(data);795796data[0] = k_EPS5ReportIdBluetoothEffects;797data[1] = 0x02; // Magic value798799if (SDL_HIDAPI_LockRumble()) {800SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));801}802} else {803// We can't even send an invalid effects packet, or it will put the controller in enhanced mode804if (device->num_joysticks > 0) {805HIDAPI_JoystickDisconnected(device, device->joysticks[0]);806}807}808}809810static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx)811{812if (ctx->enhanced_mode_available) {813return;814}815ctx->enhanced_mode_available = true;816817if (ctx->touchpad_supported) {818SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);819ctx->report_touchpad = true;820}821822if (ctx->sensors_supported) {823// Standard DualSense sensor update rate is 250 Hz over USB824float update_rate = 250.0f;825826if (ctx->device->is_bluetooth) {827// Bluetooth sensor update rate appears to be 1000 Hz828update_rate = 1000.0f;829} else if (SDL_IsJoystickDualSenseEdge(ctx->device->vendor_id, ctx->device->product_id)) {830// DualSense Edge sensor update rate is 1000 Hz over USB831update_rate = 1000.0f;832}833834SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, update_rate);835SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, update_rate);836}837838ctx->report_battery = true;839840HIDAPI_UpdateDeviceProperties(ctx->device);841}842843static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)844{845HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);846847if (!ctx->enhanced_mode) {848ctx->enhanced_mode = true;849850// Switch into enhanced report mode851HIDAPI_DriverPS5_UpdateEffects(ctx, 0, false);852853// Update the light effects854HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);855}856}857858static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint)859{860switch (enhanced_report_hint) {861case PS5_ENHANCED_REPORT_HINT_OFF:862// Nothing to do, enhanced mode is a one-way ticket863break;864case PS5_ENHANCED_REPORT_HINT_ON:865HIDAPI_DriverPS5_SetEnhancedMode(ctx);866break;867case PS5_ENHANCED_REPORT_HINT_AUTO:868HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);869break;870}871ctx->enhanced_report_hint = enhanced_report_hint;872}873874static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx)875{876ctx->enhanced_reports = true;877878if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {879HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);880}881}882883static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx)884{885if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {886HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);887}888}889890static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)891{892SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;893894if (ctx->device->is_bluetooth) {895if (hint && SDL_strcasecmp(hint, "auto") == 0) {896HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO);897} else if (SDL_GetStringBoolean(hint, true)) {898HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);899} else {900HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF);901}902} else {903HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);904}905}906907static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)908{909SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;910bool player_lights = SDL_GetStringBoolean(hint, true);911912if (player_lights != ctx->player_lights) {913ctx->player_lights = player_lights;914915HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, false);916}917}918919static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)920{921SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;922923if (!ctx->joystick) {924return;925}926927ctx->player_index = player_index;928929// This will set the new LED state based on the new player index930// SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode931HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);932}933934static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)935{936SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;937938SDL_AssertJoysticksLocked();939940ctx->joystick = joystick;941ctx->last_packet = SDL_GetTicks();942ctx->report_sensors = false;943ctx->report_touchpad = false;944ctx->rumble_left = 0;945ctx->rumble_right = 0;946ctx->color_set = false;947ctx->led_reset_state = k_EDS5LEDResetStateNone;948SDL_zero(ctx->last_state);949950// Initialize player index (needed for setting LEDs)951ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);952ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, true);953954// Initialize the joystick capabilities955if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {956joystick->nbuttons = 17; // paddles and touchpad and microphone957} else if (ctx->touchpad_supported) {958joystick->nbuttons = 13; // touchpad and microphone959} else {960joystick->nbuttons = 11;961}962joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;963joystick->nhats = 1;964joystick->firmware_version = ctx->firmware_version;965966SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,967SDL_PS5EnhancedReportsChanged, ctx);968SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,969SDL_PS5PlayerLEDHintChanged, ctx);970971return true;972}973974static bool HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)975{976SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;977978if (!ctx->vibration_supported) {979return SDL_Unsupported();980}981982if (!ctx->rumble_left && !ctx->rumble_right) {983HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, true);984}985986ctx->rumble_left = (low_frequency_rumble >> 8);987ctx->rumble_right = (high_frequency_rumble >> 8);988989return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, true);990}991992static bool HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)993{994return SDL_Unsupported();995}996997static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)998{999SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;1000Uint32 result = 0;10011002if (ctx->enhanced_mode_available) {1003if (ctx->lightbar_supported) {1004result |= SDL_JOYSTICK_CAP_RGB_LED;1005}1006if (ctx->playerled_supported) {1007result |= SDL_JOYSTICK_CAP_PLAYER_LED;1008}1009if (ctx->vibration_supported) {1010result |= SDL_JOYSTICK_CAP_RUMBLE;1011}1012}10131014return result;1015}10161017static bool HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)1018{1019SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;10201021if (!ctx->lightbar_supported) {1022return SDL_Unsupported();1023}10241025ctx->color_set = true;1026ctx->led_red = red;1027ctx->led_green = green;1028ctx->led_blue = blue;10291030return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, true);1031}10321033static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage)1034{1035Uint8 data[78];1036int report_size, offset;1037Uint8 *pending_data;1038int *pending_size;1039int maximum_size;10401041if (!ctx->effects_supported) {1042// We shouldn't be sending packets to this controller1043return SDL_Unsupported();1044}10451046if (!ctx->enhanced_mode) {1047if (application_usage) {1048HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);10491050// Wait briefly before sending additional effects1051SDL_Delay(10);1052}10531054if (!ctx->enhanced_mode) {1055// We're not in enhanced mode, effects aren't allowed1056return SDL_Unsupported();1057}1058}10591060SDL_zeroa(data);10611062if (ctx->device->is_bluetooth) {1063data[0] = k_EPS5ReportIdBluetoothEffects;1064data[1] = 0x02; // Magic value10651066report_size = 78;1067offset = 2;1068} else {1069data[0] = k_EPS5ReportIdUsbEffects;10701071report_size = 48;1072offset = 1;1073}10741075SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));10761077if (ctx->device->is_bluetooth) {1078// Bluetooth reports need a CRC at the end of the packet (at least on Linux)1079Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation1080Uint32 unCRC;1081unCRC = SDL_crc32(0, &ubHdr, 1);1082unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));1083SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));1084}10851086if (!SDL_HIDAPI_LockRumble()) {1087return false;1088}10891090// See if we can update an existing pending request1091if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) {1092DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset];1093DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];1094if (report_size == *pending_size &&1095effects->ucEnableBits1 == pending_effects->ucEnableBits1 &&1096effects->ucEnableBits2 == pending_effects->ucEnableBits2) {1097// We're simply updating the data for this request1098SDL_memcpy(pending_data, data, report_size);1099SDL_HIDAPI_UnlockRumble();1100return true;1101}1102}11031104if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) {1105return false;1106}11071108return true;1109}11101111static bool HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)1112{1113SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;11141115return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, true);1116}11171118static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)1119{1120SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;11211122HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);11231124if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {1125return SDL_Unsupported();1126}11271128if (enabled) {1129HIDAPI_DriverPS5_LoadCalibrationData(device);1130}1131ctx->report_sensors = enabled;11321133return true;1134}11351136static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5SimpleStatePacket_t *packet, Uint64 timestamp)1137{1138Sint16 axis;11391140if (ctx->last_state.simple.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {1141{1142Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);11431144SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));1145SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));1146SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));1147SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));1148}1149{1150Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);1151Uint8 hat;11521153switch (data) {1154case 0:1155hat = SDL_HAT_UP;1156break;1157case 1:1158hat = SDL_HAT_RIGHTUP;1159break;1160case 2:1161hat = SDL_HAT_RIGHT;1162break;1163case 3:1164hat = SDL_HAT_RIGHTDOWN;1165break;1166case 4:1167hat = SDL_HAT_DOWN;1168break;1169case 5:1170hat = SDL_HAT_LEFTDOWN;1171break;1172case 6:1173hat = SDL_HAT_LEFT;1174break;1175case 7:1176hat = SDL_HAT_LEFTUP;1177break;1178default:1179hat = SDL_HAT_CENTERED;1180break;1181}1182SDL_SendJoystickHat(timestamp, joystick, 0, hat);1183}1184}11851186if (ctx->last_state.simple.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {1187Uint8 data = packet->rgucButtonsHatAndCounter[1];11881189SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));1190SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));1191SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));1192SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));1193SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));1194SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));1195}11961197if (ctx->last_state.simple.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {1198Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);11991200SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));1201SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));1202}12031204if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) {1205axis = SDL_JOYSTICK_AXIS_MAX;1206} else {1207axis = ((int)packet->ucTriggerLeft * 257) - 32768;1208}1209SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);1210if (packet->ucTriggerRight == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x08)) {1211axis = SDL_JOYSTICK_AXIS_MAX;1212} else {1213axis = ((int)packet->ucTriggerRight * 257) - 32768;1214}1215SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);1216axis = ((int)packet->ucLeftJoystickX * 257) - 32768;1217SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1218axis = ((int)packet->ucLeftJoystickY * 257) - 32768;1219SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1220axis = ((int)packet->ucRightJoystickX * 257) - 32768;1221SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1222axis = ((int)packet->ucRightJoystickY * 257) - 32768;1223SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);12241225SDL_memcpy(&ctx->last_state.simple, packet, sizeof(ctx->last_state.simple));1226}12271228static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketCommon_t *packet, Uint64 timestamp)1229{1230Sint16 axis;12311232if (ctx->last_state.state.rgucButtonsAndHat[0] != packet->rgucButtonsAndHat[0]) {1233{1234Uint8 data = (packet->rgucButtonsAndHat[0] >> 4);12351236SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));1237SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));1238SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));1239SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));1240}1241{1242Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F);1243Uint8 hat;12441245switch (data) {1246case 0:1247hat = SDL_HAT_UP;1248break;1249case 1:1250hat = SDL_HAT_RIGHTUP;1251break;1252case 2:1253hat = SDL_HAT_RIGHT;1254break;1255case 3:1256hat = SDL_HAT_RIGHTDOWN;1257break;1258case 4:1259hat = SDL_HAT_DOWN;1260break;1261case 5:1262hat = SDL_HAT_LEFTDOWN;1263break;1264case 6:1265hat = SDL_HAT_LEFT;1266break;1267case 7:1268hat = SDL_HAT_LEFTUP;1269break;1270default:1271hat = SDL_HAT_CENTERED;1272break;1273}1274SDL_SendJoystickHat(timestamp, joystick, 0, hat);1275}1276}12771278if (ctx->last_state.state.rgucButtonsAndHat[1] != packet->rgucButtonsAndHat[1]) {1279Uint8 data = packet->rgucButtonsAndHat[1];12801281SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));1282SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));1283SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));1284SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));1285SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));1286SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));1287}12881289if (ctx->last_state.state.rgucButtonsAndHat[2] != packet->rgucButtonsAndHat[2]) {1290Uint8 data = packet->rgucButtonsAndHat[2];12911292SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));1293SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));1294SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, ((data & 0x04) != 0));1295SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, ((data & 0x10) != 0));1296SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, ((data & 0x20) != 0));1297SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, ((data & 0x40) != 0));1298SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, ((data & 0x80) != 0));1299}13001301if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) {1302axis = SDL_JOYSTICK_AXIS_MAX;1303} else {1304axis = ((int)packet->ucTriggerLeft * 257) - 32768;1305}1306SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);1307if (packet->ucTriggerRight == 0 && (packet->rgucButtonsAndHat[1] & 0x08)) {1308axis = SDL_JOYSTICK_AXIS_MAX;1309} else {1310axis = ((int)packet->ucTriggerRight * 257) - 32768;1311}1312SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);1313axis = ((int)packet->ucLeftJoystickX * 257) - 32768;1314SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1315axis = ((int)packet->ucLeftJoystickY * 257) - 32768;1316SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1317axis = ((int)packet->ucRightJoystickX * 257) - 32768;1318SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1319axis = ((int)packet->ucRightJoystickY * 257) - 32768;1320SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);13211322if (ctx->report_sensors) {1323Uint64 sensor_timestamp;1324float data[3];13251326if (ctx->use_alternate_report) {1327// 16-bit timestamp1328Uint32 delta;1329Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0],1330packet->rgucSensorTimestamp[1]);1331if (ctx->last_tick < tick) {1332delta = (tick - ctx->last_tick);1333} else {1334delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);1335}1336ctx->last_tick = tick;1337ctx->sensor_ticks += delta;13381339// Sensor timestamp is in 1us units1340sensor_timestamp = SDL_US_TO_NS(ctx->sensor_ticks);1341} else {1342// 32-bit timestamp1343Uint32 delta;1344Uint32 tick = LOAD32(packet->rgucSensorTimestamp[0],1345packet->rgucSensorTimestamp[1],1346packet->rgucSensorTimestamp[2],1347packet->rgucSensorTimestamp[3]);1348if (ctx->last_tick < tick) {1349delta = (tick - ctx->last_tick);1350} else {1351delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1);1352}1353ctx->last_tick = tick;1354ctx->sensor_ticks += delta;13551356// Sensor timestamp is in 0.33us units1357sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US) / 3;1358}13591360data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));1361data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));1362data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));1363SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);13641365data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));1366data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));1367data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));1368SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);1369}1370}13711372static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp)1373{1374static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 19201375static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 10701376bool touchpad_down;1377int touchpad_x, touchpad_y;13781379if (ctx->report_touchpad) {1380touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);1381touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);1382touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);1383SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);13841385touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);1386touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);1387touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);1388SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);1389}13901391if (ctx->report_battery) {1392SDL_PowerState state;1393int percent;1394Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F;1395Uint8 level = (packet->ucBatteryLevel & 0x0F);13961397switch (status) {1398case 0:1399state = SDL_POWERSTATE_ON_BATTERY;1400percent = SDL_min(level * 10 + 5, 100);1401break;1402case 1:1403state = SDL_POWERSTATE_CHARGING;1404percent = SDL_min(level * 10 + 5, 100);1405break;1406case 2:1407state = SDL_POWERSTATE_CHARGED;1408percent = 100;1409break;1410default:1411state = SDL_POWERSTATE_UNKNOWN;1412percent = 0;1413break;1414}1415SDL_SendJoystickPowerInfo(joystick, state, percent);1416}14171418HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);14191420SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));1421}14221423static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp)1424{1425static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 19201426static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 10701427bool touchpad_down;1428int touchpad_x, touchpad_y;14291430if (ctx->report_touchpad) {1431touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);1432touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);1433touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);1434SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);14351436touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);1437touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);1438touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);1439SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);1440}14411442HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);14431444SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));1445}14461447static bool VerifyCRC(Uint8 *data, int size)1448{1449Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation1450Uint32 unCRC, unPacketCRC;1451Uint8 *packetCRC = data + size - sizeof(unPacketCRC);1452unCRC = SDL_crc32(0, &ubHdr, 1);1453unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));14541455unPacketCRC = LOAD32(packetCRC[0],1456packetCRC[1],1457packetCRC[2],1458packetCRC[3]);1459return (unCRC == unPacketCRC);1460}14611462static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size)1463{1464switch (data[0]) {1465case k_EPS5ReportIdState:1466if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) {1467// The report timestamp doesn't change when the controller isn't connected1468PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1];1469if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) {1470return false;1471}1472if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 &&1473ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 &&1474ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) {1475// We don't have any state to compare yet, go ahead and copy it1476SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t));1477return false;1478}1479}1480return true;14811482case k_EPS5ReportIdBluetoothState:1483if (VerifyCRC(data, size)) {1484return true;1485}1486break;1487default:1488break;1489}1490return false;1491}14921493static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)1494{1495SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;1496SDL_Joystick *joystick = NULL;1497Uint8 data[USB_PACKET_LENGTH * 2];1498int size;1499int packet_count = 0;1500Uint64 now = SDL_GetTicks();15011502if (device->num_joysticks > 0) {1503joystick = SDL_GetJoystickFromID(device->joysticks[0]);1504}15051506while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {1507Uint64 timestamp = SDL_GetTicksNS();15081509#ifdef DEBUG_PS5_PROTOCOL1510HIDAPI_DumpPacket("PS5 packet: size = %d", data, size);1511#endif1512if (!HIDAPI_DriverPS5_IsPacketValid(ctx, data, size)) {1513continue;1514}15151516++packet_count;1517ctx->last_packet = now;15181519if (!joystick) {1520continue;1521}15221523switch (data[0]) {1524case k_EPS5ReportIdState:1525if (size == 10 || size == 78) {1526HIDAPI_DriverPS5_HandleSimpleStatePacket(joystick, device->dev, ctx, (PS5SimpleStatePacket_t *)&data[1], timestamp);1527} else {1528if (ctx->use_alternate_report) {1529HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[1], timestamp);1530} else {1531HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[1], timestamp);1532}1533}1534break;1535case k_EPS5ReportIdBluetoothState:1536// This is the extended report, we can enable effects now in auto mode1537HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx);15381539if (ctx->use_alternate_report) {1540HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2], timestamp);1541} else {1542HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2], timestamp);1543}1544if (ctx->led_reset_state == k_EDS5LEDResetStatePending) {1545HIDAPI_DriverPS5_CheckPendingLEDReset(ctx);1546}1547break;1548default:1549#ifdef DEBUG_JOYSTICK1550SDL_Log("Unknown PS5 packet: 0x%.2x", data[0]);1551#endif1552break;1553}1554}15551556if (device->is_bluetooth) {1557if (packet_count == 0) {1558// Check to see if it looks like the device disconnected1559if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {1560// Send an empty output report to tickle the Bluetooth stack1561HIDAPI_DriverPS5_TickleBluetooth(device);1562ctx->last_packet = now;1563}1564} else {1565// Reconnect the Bluetooth device once the USB device is gone1566if (device->num_joysticks == 0 &&1567!HIDAPI_HasConnectedUSBDevice(device->serial)) {1568HIDAPI_JoystickConnected(device, NULL);1569}1570}1571}15721573if (ctx->is_nacon_dongle) {1574if (packet_count == 0) {1575if (device->num_joysticks > 0) {1576// Check to see if it looks like the device disconnected1577if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {1578HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1579}1580}1581} else {1582if (device->num_joysticks == 0) {1583HIDAPI_JoystickConnected(device, NULL);1584}1585}1586}15871588if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {1589// Read error, device is disconnected1590HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1591}1592return (size >= 0);1593}15941595static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1596{1597SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;15981599SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,1600SDL_PS5EnhancedReportsChanged, ctx);16011602SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,1603SDL_PS5PlayerLEDHintChanged, ctx);16041605ctx->joystick = NULL;16061607ctx->report_sensors = false;1608ctx->enhanced_mode = false;1609ctx->enhanced_mode_available = false;1610}16111612static void HIDAPI_DriverPS5_FreeDevice(SDL_HIDAPI_Device *device)1613{1614}16151616SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = {1617SDL_HINT_JOYSTICK_HIDAPI_PS5,1618true,1619HIDAPI_DriverPS5_RegisterHints,1620HIDAPI_DriverPS5_UnregisterHints,1621HIDAPI_DriverPS5_IsEnabled,1622HIDAPI_DriverPS5_IsSupportedDevice,1623HIDAPI_DriverPS5_InitDevice,1624HIDAPI_DriverPS5_GetDevicePlayerIndex,1625HIDAPI_DriverPS5_SetDevicePlayerIndex,1626HIDAPI_DriverPS5_UpdateDevice,1627HIDAPI_DriverPS5_OpenJoystick,1628HIDAPI_DriverPS5_RumbleJoystick,1629HIDAPI_DriverPS5_RumbleJoystickTriggers,1630HIDAPI_DriverPS5_GetJoystickCapabilities,1631HIDAPI_DriverPS5_SetJoystickLED,1632HIDAPI_DriverPS5_SendJoystickEffect,1633HIDAPI_DriverPS5_SetJoystickSensorsEnabled,1634HIDAPI_DriverPS5_CloseJoystick,1635HIDAPI_DriverPS5_FreeDevice,1636};16371638#endif // SDL_JOYSTICK_HIDAPI_PS516391640#endif // SDL_JOYSTICK_HIDAPI164116421643