Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_ps5.c
9906 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;253Uint8 rumble_left;254Uint8 rumble_right;255bool color_set;256Uint8 led_red;257Uint8 led_green;258Uint8 led_blue;259EDS5LEDResetState led_reset_state;260Uint64 sensor_ticks;261Uint32 last_tick;262union263{264PS5SimpleStatePacket_t simple;265PS5StatePacketCommon_t state;266PS5StatePacketAlt_t alt_state;267PS5StatePacket_t full_state;268Uint8 data[64];269} last_state;270} SDL_DriverPS5_Context;271272static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage);273274static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata)275{276SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);277}278279static void HIDAPI_DriverPS5_UnregisterHints(SDL_HintCallback callback, void *userdata)280{281SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5, callback, userdata);282}283284static bool HIDAPI_DriverPS5_IsEnabled(void)285{286return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));287}288289static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)290{291SDL_memset(report, 0, length);292report[0] = report_id;293return SDL_hid_get_feature_report(dev, report, length);294}295296static 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)297{298Uint8 data[USB_PACKET_LENGTH];299int size;300301if (type == SDL_GAMEPAD_TYPE_PS5) {302return true;303}304305if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {306if (device && device->dev) {307size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));308if (size == 48 && data[2] == 0x28) {309// Supported third party controller310return true;311} else {312return false;313}314} else {315// Might be supported by this driver, enumerate and find out316return true;317}318}319return false;320}321322static void SetLedsForPlayerIndex(DS5EffectsState_t *effects, int player_index)323{324/* This list is the same as what hid-sony.c uses in the Linux kernel.325The first 4 values correspond to what the PS4 assigns.326*/327static const Uint8 colors[7][3] = {328{ 0x00, 0x00, 0x40 }, // Blue329{ 0x40, 0x00, 0x00 }, // Red330{ 0x00, 0x40, 0x00 }, // Green331{ 0x20, 0x00, 0x20 }, // Pink332{ 0x20, 0x10, 0x00 }, // Orange333{ 0x00, 0x10, 0x10 }, // Teal334{ 0x10, 0x10, 0x10 } // White335};336337if (player_index >= 0) {338player_index %= SDL_arraysize(colors);339} else {340player_index = 0;341}342343effects->ucLedRed = colors[player_index][0];344effects->ucLedGreen = colors[player_index][1];345effects->ucLedBlue = colors[player_index][2];346}347348static void SetLightsForPlayerIndex(DS5EffectsState_t *effects, int player_index)349{350static const Uint8 lights[] = {3510x04,3520x0A,3530x15,3540x1B,3550x1F356};357358if (player_index >= 0) {359// Bitmask, 0x1F enables all lights, 0x20 changes instantly instead of fade360player_index %= SDL_arraysize(lights);361effects->ucPadLights = lights[player_index] | 0x20;362} else {363effects->ucPadLights = 0x00;364}365}366367static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)368{369SDL_DriverPS5_Context *ctx;370Uint8 data[USB_PACKET_LENGTH * 2];371int size;372char serial[18];373SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;374375ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx));376if (!ctx) {377return false;378}379ctx->device = device;380381device->context = ctx;382383if (device->serial && SDL_strlen(device->serial) == 12) {384int i, j;385386j = -1;387for (i = 0; i < 12; i += 2) {388j += 1;389SDL_memmove(&serial[j], &device->serial[i], 2);390j += 2;391serial[j] = '-';392}393serial[j] = '\0';394} else {395serial[0] = '\0';396}397398// Read a report to see what mode we're in399size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);400#ifdef DEBUG_PS5_PROTOCOL401if (size > 0) {402HIDAPI_DumpPacket("PS5 first packet: size = %d", data, size);403} else {404SDL_Log("PS5 first packet: size = %d", size);405}406#endif407if (size == 64) {408// Connected over USB409ctx->enhanced_reports = true;410} else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) {411// Connected over Bluetooth, using enhanced reports412ctx->enhanced_reports = true;413} else {414// Connected over Bluetooth, using simple reports (DirectInput enabled)415}416417if (device->vendor_id == USB_VENDOR_SONY && ctx->enhanced_reports) {418/* Read the serial number (Bluetooth address in reverse byte order)419This will also enable enhanced reports over Bluetooth420*/421if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber, data, sizeof(data)) >= 7) {422(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",423data[6], data[5], data[4], data[3], data[2], data[1]);424}425426/* Read the firmware version427This will also enable enhanced reports over Bluetooth428*/429if (ReadFeatureReport(device->dev, k_EPS5FeatureReportIdFirmwareInfo, data, USB_PACKET_LENGTH) >= 46) {430ctx->firmware_version = (Uint16)data[44] | ((Uint16)data[45] << 8);431}432}433434// Get the device capabilities435if (device->vendor_id == USB_VENDOR_SONY) {436ctx->sensors_supported = true;437ctx->lightbar_supported = true;438ctx->vibration_supported = true;439ctx->playerled_supported = true;440ctx->touchpad_supported = true;441} else {442// Third party controller capability request443size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCapabilities, data, sizeof(data));444if (size == 48 && data[2] == 0x28) {445Uint8 capabilities = data[4];446Uint8 capabilities2 = data[20];447Uint8 device_type = data[5];448449#ifdef DEBUG_PS5_PROTOCOL450HIDAPI_DumpPacket("PS5 capabilities: size = %d", data, size);451#endif452if (capabilities & 0x02) {453ctx->sensors_supported = true;454}455if (capabilities & 0x04) {456ctx->lightbar_supported = true;457}458if (capabilities & 0x08) {459ctx->vibration_supported = true;460}461if (capabilities & 0x40) {462ctx->touchpad_supported = true;463}464if (capabilities2 & 0x80) {465ctx->playerled_supported = true;466}467468switch (device_type) {469case 0x00:470joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;471break;472case 0x01:473joystick_type = SDL_JOYSTICK_TYPE_GUITAR;474break;475case 0x02:476joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;477break;478case 0x06:479joystick_type = SDL_JOYSTICK_TYPE_WHEEL;480break;481case 0x07:482joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;483break;484case 0x08:485joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;486break;487default:488joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;489break;490}491492ctx->use_alternate_report = true;493494if (device->vendor_id == USB_VENDOR_NACON_ALT &&495(device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRED ||496device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS)) {497// This doesn't report vibration capability, but it can do rumble498ctx->vibration_supported = true;499}500} else if (device->vendor_id == USB_VENDOR_RAZER &&501(device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRED ||502device->product_id == USB_PRODUCT_RAZER_WOLVERINE_V2_PRO_PS5_WIRELESS)) {503// The Razer Wolverine V2 Pro doesn't respond to the detection protocol, but has a touchpad and sensors and no vibration504ctx->sensors_supported = true;505ctx->touchpad_supported = true;506ctx->use_alternate_report = true;507} else if (device->vendor_id == USB_VENDOR_RAZER &&508device->product_id == USB_PRODUCT_RAZER_KITSUNE) {509// The Razer Kitsune doesn't respond to the detection protocol, but has a touchpad510joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;511ctx->touchpad_supported = true;512ctx->use_alternate_report = true;513}514}515ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported || ctx->playerled_supported);516517if (device->vendor_id == USB_VENDOR_NACON_ALT &&518device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS5_WIRELESS) {519ctx->is_nacon_dongle = true;520}521522device->joystick_type = joystick_type;523device->type = SDL_GAMEPAD_TYPE_PS5;524if (device->vendor_id == USB_VENDOR_SONY) {525if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {526HIDAPI_SetDeviceName(device, "DualSense Edge Wireless Controller");527} else {528HIDAPI_SetDeviceName(device, "DualSense Wireless Controller");529}530}531HIDAPI_SetDeviceSerial(device, serial);532533if (ctx->is_nacon_dongle) {534// We don't know if this is connected yet, wait for reports535return true;536}537538// Prefer the USB device over the Bluetooth device539if (device->is_bluetooth) {540if (HIDAPI_HasConnectedUSBDevice(device->serial)) {541return true;542}543} else {544HIDAPI_DisconnectBluetoothDevice(device->serial);545}546return HIDAPI_JoystickConnected(device, NULL);547}548549static int HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)550{551return -1;552}553554static void HIDAPI_DriverPS5_LoadCalibrationData(SDL_HIDAPI_Device *device)555{556SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;557int i, size;558Uint8 data[USB_PACKET_LENGTH];559560size = ReadFeatureReport(device->dev, k_EPS5FeatureReportIdCalibration, data, sizeof(data));561if (size < 35) {562#ifdef DEBUG_PS5_CALIBRATION563SDL_Log("Short read of calibration data: %d, ignoring calibration", size);564#endif565return;566}567568{569Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;570Sint16 sGyroPitchPlus, sGyroPitchMinus;571Sint16 sGyroYawPlus, sGyroYawMinus;572Sint16 sGyroRollPlus, sGyroRollMinus;573Sint16 sGyroSpeedPlus, sGyroSpeedMinus;574575Sint16 sAccXPlus, sAccXMinus;576Sint16 sAccYPlus, sAccYMinus;577Sint16 sAccZPlus, sAccZMinus;578579float flNumerator;580Sint16 sRange2g;581582#ifdef DEBUG_PS5_CALIBRATION583HIDAPI_DumpPacket("PS5 calibration packet: size = %d", data, size);584#endif585586sGyroPitchBias = LOAD16(data[1], data[2]);587sGyroYawBias = LOAD16(data[3], data[4]);588sGyroRollBias = LOAD16(data[5], data[6]);589590sGyroPitchPlus = LOAD16(data[7], data[8]);591sGyroPitchMinus = LOAD16(data[9], data[10]);592sGyroYawPlus = LOAD16(data[11], data[12]);593sGyroYawMinus = LOAD16(data[13], data[14]);594sGyroRollPlus = LOAD16(data[15], data[16]);595sGyroRollMinus = LOAD16(data[17], data[18]);596597sGyroSpeedPlus = LOAD16(data[19], data[20]);598sGyroSpeedMinus = LOAD16(data[21], data[22]);599600sAccXPlus = LOAD16(data[23], data[24]);601sAccXMinus = LOAD16(data[25], data[26]);602sAccYPlus = LOAD16(data[27], data[28]);603sAccYMinus = LOAD16(data[29], data[30]);604sAccZPlus = LOAD16(data[31], data[32]);605sAccZMinus = LOAD16(data[33], data[34]);606607flNumerator = (sGyroSpeedPlus + sGyroSpeedMinus) * GYRO_RES_PER_DEGREE;608ctx->calibration[0].bias = sGyroPitchBias;609ctx->calibration[0].sensitivity = flNumerator / (sGyroPitchPlus - sGyroPitchMinus);610611ctx->calibration[1].bias = sGyroYawBias;612ctx->calibration[1].sensitivity = flNumerator / (sGyroYawPlus - sGyroYawMinus);613614ctx->calibration[2].bias = sGyroRollBias;615ctx->calibration[2].sensitivity = flNumerator / (sGyroRollPlus - sGyroRollMinus);616617sRange2g = sAccXPlus - sAccXMinus;618ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;619ctx->calibration[3].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;620621sRange2g = sAccYPlus - sAccYMinus;622ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;623ctx->calibration[4].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;624625sRange2g = sAccZPlus - sAccZMinus;626ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;627ctx->calibration[5].sensitivity = 2.0f * ACCEL_RES_PER_G / (float)sRange2g;628629ctx->hardware_calibration = true;630for (i = 0; i < 6; ++i) {631float divisor = (i < 3 ? 64.0f : 1.0f);632#ifdef DEBUG_PS5_CALIBRATION633SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].sensitivity);634#endif635// Some controllers have a bad calibration636if ((SDL_abs(ctx->calibration[i].bias) > 1024) || (SDL_fabsf(1.0f - ctx->calibration[i].sensitivity / divisor) > 0.5f)) {637#ifdef DEBUG_PS5_CALIBRATION638SDL_Log("invalid calibration, ignoring");639#endif640ctx->hardware_calibration = false;641}642}643}644}645646static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, int index, Sint16 value)647{648float result;649650if (ctx->hardware_calibration) {651IMUCalibrationData *calibration = &ctx->calibration[index];652653result = (value - calibration->bias) * calibration->sensitivity;654} else if (index < 3) {655result = value * 64.f;656} else {657result = value;658}659660// Convert the raw data to the units expected by SDL661if (index < 3) {662result = (result / GYRO_RES_PER_DEGREE) * SDL_PI_F / 180.0f;663} else {664result = (result / ACCEL_RES_PER_G) * SDL_STANDARD_GRAVITY;665}666return result;667}668669static bool HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, bool application_usage)670{671DS5EffectsState_t effects;672673// Make sure the Bluetooth connection sequence has completed before sending LED color change674if (ctx->device->is_bluetooth && ctx->enhanced_reports &&675(effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) {676if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) {677ctx->led_reset_state = k_EDS5LEDResetStatePending;678return true;679}680}681682SDL_zero(effects);683684if (ctx->vibration_supported) {685if (ctx->rumble_left || ctx->rumble_right) {686if (ctx->firmware_version < 0x0224) {687effects.ucEnableBits1 |= 0x01; // Enable rumble emulation688689// Shift to reduce effective rumble strength to match Xbox controllers690effects.ucRumbleLeft = ctx->rumble_left >> 1;691effects.ucRumbleRight = ctx->rumble_right >> 1;692} else {693effects.ucEnableBits3 |= 0x04; // Enable improved rumble emulation on 2.24 firmware and newer694695effects.ucRumbleLeft = ctx->rumble_left;696effects.ucRumbleRight = ctx->rumble_right;697}698effects.ucEnableBits1 |= 0x02; // Disable audio haptics699} else {700// Leaving emulated rumble bits off will restore audio haptics701}702703if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {704effects.ucEnableBits1 |= 0x02; // Disable audio haptics705}706if ((effect_mask & k_EDS5EffectRumble) != 0) {707// Already handled above708}709}710if (ctx->lightbar_supported) {711if ((effect_mask & k_EDS5EffectLEDReset) != 0) {712effects.ucEnableBits2 |= 0x08; // Reset LED state713}714if ((effect_mask & k_EDS5EffectLED) != 0) {715effects.ucEnableBits2 |= 0x04; // Enable LED color716717// Populate the LED state with the appropriate color from our lookup table718if (ctx->color_set) {719effects.ucLedRed = ctx->led_red;720effects.ucLedGreen = ctx->led_green;721effects.ucLedBlue = ctx->led_blue;722} else {723SetLedsForPlayerIndex(&effects, ctx->player_index);724}725}726}727if (ctx->playerled_supported) {728if ((effect_mask & k_EDS5EffectPadLights) != 0) {729effects.ucEnableBits2 |= 0x10; // Enable touchpad lights730731if (ctx->player_lights) {732SetLightsForPlayerIndex(&effects, ctx->player_index);733} else {734effects.ucPadLights = 0x00;735}736}737}738if ((effect_mask & k_EDS5EffectMicLight) != 0) {739effects.ucEnableBits2 |= 0x01; // Enable microphone light740741effects.ucMicLightMode = 0; // Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse742}743744return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);745}746747static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx)748{749bool led_reset_complete = false;750751if (ctx->enhanced_reports && ctx->sensors_supported && !ctx->use_alternate_report) {752const PS5StatePacketCommon_t *packet = &ctx->last_state.state;753754// Check the timer to make sure the Bluetooth connection LED animation is complete755const Uint32 connection_complete = 10200000;756Uint32 timestamp = LOAD32(packet->rgucSensorTimestamp[0],757packet->rgucSensorTimestamp[1],758packet->rgucSensorTimestamp[2],759packet->rgucSensorTimestamp[3]);760if (timestamp >= connection_complete) {761led_reset_complete = true;762}763} else {764// We don't know how to check the timer, just assume it's complete for now765led_reset_complete = true;766}767768if (led_reset_complete) {769HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, false);770771ctx->led_reset_state = k_EDS5LEDResetStateComplete;772773HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);774}775}776777static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)778{779SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;780781if (ctx->enhanced_reports) {782// This is just a dummy packet that should have no effect, since we don't set the CRC783Uint8 data[78];784785SDL_zeroa(data);786787data[0] = k_EPS5ReportIdBluetoothEffects;788data[1] = 0x02; // Magic value789790if (SDL_HIDAPI_LockRumble()) {791SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));792}793} else {794// We can't even send an invalid effects packet, or it will put the controller in enhanced mode795if (device->num_joysticks > 0) {796HIDAPI_JoystickDisconnected(device, device->joysticks[0]);797}798}799}800801static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx)802{803if (ctx->enhanced_mode_available) {804return;805}806ctx->enhanced_mode_available = true;807808if (ctx->touchpad_supported) {809SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);810ctx->report_touchpad = true;811}812813if (ctx->sensors_supported) {814if (ctx->device->is_bluetooth) {815// Bluetooth sensor update rate appears to be 1000 Hz816SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f);817SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f);818} else {819SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f);820SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f);821}822}823824ctx->report_battery = true;825826HIDAPI_UpdateDeviceProperties(ctx->device);827}828829static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)830{831HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);832833if (!ctx->enhanced_mode) {834ctx->enhanced_mode = true;835836// Switch into enhanced report mode837HIDAPI_DriverPS5_UpdateEffects(ctx, 0, false);838839// Update the light effects840HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);841}842}843844static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint)845{846switch (enhanced_report_hint) {847case PS5_ENHANCED_REPORT_HINT_OFF:848// Nothing to do, enhanced mode is a one-way ticket849break;850case PS5_ENHANCED_REPORT_HINT_ON:851HIDAPI_DriverPS5_SetEnhancedMode(ctx);852break;853case PS5_ENHANCED_REPORT_HINT_AUTO:854HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);855break;856}857ctx->enhanced_report_hint = enhanced_report_hint;858}859860static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx)861{862ctx->enhanced_reports = true;863864if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {865HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);866}867}868869static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx)870{871if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {872HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);873}874}875876static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)877{878SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;879880if (ctx->device->is_bluetooth) {881if (hint && SDL_strcasecmp(hint, "auto") == 0) {882HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO);883} else if (SDL_GetStringBoolean(hint, true)) {884HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);885} else {886HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF);887}888} else {889HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);890}891}892893static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)894{895SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;896bool player_lights = SDL_GetStringBoolean(hint, true);897898if (player_lights != ctx->player_lights) {899ctx->player_lights = player_lights;900901HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, false);902}903}904905static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)906{907SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;908909if (!ctx->joystick) {910return;911}912913ctx->player_index = player_index;914915// This will set the new LED state based on the new player index916// SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode917HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), false);918}919920static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)921{922SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;923924SDL_AssertJoysticksLocked();925926ctx->joystick = joystick;927ctx->last_packet = SDL_GetTicks();928ctx->report_sensors = false;929ctx->report_touchpad = false;930ctx->rumble_left = 0;931ctx->rumble_right = 0;932ctx->color_set = false;933ctx->led_reset_state = k_EDS5LEDResetStateNone;934SDL_zero(ctx->last_state);935936// Initialize player index (needed for setting LEDs)937ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);938ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, true);939940// Initialize the joystick capabilities941if (SDL_IsJoystickDualSenseEdge(device->vendor_id, device->product_id)) {942joystick->nbuttons = 17; // paddles and touchpad and microphone943} else if (ctx->touchpad_supported) {944joystick->nbuttons = 13; // touchpad and microphone945} else {946joystick->nbuttons = 11;947}948joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;949joystick->nhats = 1;950joystick->firmware_version = ctx->firmware_version;951952SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,953SDL_PS5EnhancedReportsChanged, ctx);954SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,955SDL_PS5PlayerLEDHintChanged, ctx);956957return true;958}959960static bool HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)961{962SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;963964if (!ctx->vibration_supported) {965return SDL_Unsupported();966}967968if (!ctx->rumble_left && !ctx->rumble_right) {969HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, true);970}971972ctx->rumble_left = (low_frequency_rumble >> 8);973ctx->rumble_right = (high_frequency_rumble >> 8);974975return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, true);976}977978static bool HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)979{980return SDL_Unsupported();981}982983static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)984{985SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;986Uint32 result = 0;987988if (ctx->enhanced_mode_available) {989if (ctx->lightbar_supported) {990result |= SDL_JOYSTICK_CAP_RGB_LED;991}992if (ctx->playerled_supported) {993result |= SDL_JOYSTICK_CAP_PLAYER_LED;994}995if (ctx->vibration_supported) {996result |= SDL_JOYSTICK_CAP_RUMBLE;997}998}9991000return result;1001}10021003static bool HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)1004{1005SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;10061007if (!ctx->lightbar_supported) {1008return SDL_Unsupported();1009}10101011ctx->color_set = true;1012ctx->led_red = red;1013ctx->led_green = green;1014ctx->led_blue = blue;10151016return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, true);1017}10181019static bool HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, bool application_usage)1020{1021Uint8 data[78];1022int report_size, offset;1023Uint8 *pending_data;1024int *pending_size;1025int maximum_size;10261027if (!ctx->effects_supported) {1028// We shouldn't be sending packets to this controller1029return SDL_Unsupported();1030}10311032if (!ctx->enhanced_mode) {1033if (application_usage) {1034HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);1035}10361037if (!ctx->enhanced_mode) {1038// We're not in enhanced mode, effects aren't allowed1039return SDL_Unsupported();1040}1041}10421043SDL_zeroa(data);10441045if (ctx->device->is_bluetooth) {1046data[0] = k_EPS5ReportIdBluetoothEffects;1047data[1] = 0x02; // Magic value10481049report_size = 78;1050offset = 2;1051} else {1052data[0] = k_EPS5ReportIdUsbEffects;10531054report_size = 48;1055offset = 1;1056}10571058SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));10591060if (ctx->device->is_bluetooth) {1061// Bluetooth reports need a CRC at the end of the packet (at least on Linux)1062Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation1063Uint32 unCRC;1064unCRC = SDL_crc32(0, &ubHdr, 1);1065unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));1066SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));1067}10681069if (!SDL_HIDAPI_LockRumble()) {1070return false;1071}10721073// See if we can update an existing pending request1074if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) {1075DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset];1076DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];1077if (report_size == *pending_size &&1078effects->ucEnableBits1 == pending_effects->ucEnableBits1 &&1079effects->ucEnableBits2 == pending_effects->ucEnableBits2) {1080// We're simply updating the data for this request1081SDL_memcpy(pending_data, data, report_size);1082SDL_HIDAPI_UnlockRumble();1083return true;1084}1085}10861087if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) {1088return false;1089}10901091return true;1092}10931094static bool HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)1095{1096SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;10971098return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, true);1099}11001101static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)1102{1103SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;11041105HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);11061107if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {1108return SDL_Unsupported();1109}11101111if (enabled) {1112HIDAPI_DriverPS5_LoadCalibrationData(device);1113}1114ctx->report_sensors = enabled;11151116return true;1117}11181119static void HIDAPI_DriverPS5_HandleSimpleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5SimpleStatePacket_t *packet, Uint64 timestamp)1120{1121Sint16 axis;11221123if (ctx->last_state.simple.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {1124{1125Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);11261127SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));1128SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));1129SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));1130SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));1131}1132{1133Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);1134Uint8 hat;11351136switch (data) {1137case 0:1138hat = SDL_HAT_UP;1139break;1140case 1:1141hat = SDL_HAT_RIGHTUP;1142break;1143case 2:1144hat = SDL_HAT_RIGHT;1145break;1146case 3:1147hat = SDL_HAT_RIGHTDOWN;1148break;1149case 4:1150hat = SDL_HAT_DOWN;1151break;1152case 5:1153hat = SDL_HAT_LEFTDOWN;1154break;1155case 6:1156hat = SDL_HAT_LEFT;1157break;1158case 7:1159hat = SDL_HAT_LEFTUP;1160break;1161default:1162hat = SDL_HAT_CENTERED;1163break;1164}1165SDL_SendJoystickHat(timestamp, joystick, 0, hat);1166}1167}11681169if (ctx->last_state.simple.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {1170Uint8 data = packet->rgucButtonsHatAndCounter[1];11711172SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));1173SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));1174SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));1175SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));1176SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));1177SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));1178}11791180if (ctx->last_state.simple.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {1181Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);11821183SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));1184SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));1185}11861187if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x04)) {1188axis = SDL_JOYSTICK_AXIS_MAX;1189} else {1190axis = ((int)packet->ucTriggerLeft * 257) - 32768;1191}1192SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);1193if (packet->ucTriggerRight == 0 && (packet->rgucButtonsHatAndCounter[1] & 0x08)) {1194axis = SDL_JOYSTICK_AXIS_MAX;1195} else {1196axis = ((int)packet->ucTriggerRight * 257) - 32768;1197}1198SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);1199axis = ((int)packet->ucLeftJoystickX * 257) - 32768;1200SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1201axis = ((int)packet->ucLeftJoystickY * 257) - 32768;1202SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1203axis = ((int)packet->ucRightJoystickX * 257) - 32768;1204SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1205axis = ((int)packet->ucRightJoystickY * 257) - 32768;1206SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);12071208SDL_memcpy(&ctx->last_state.simple, packet, sizeof(ctx->last_state.simple));1209}12101211static void HIDAPI_DriverPS5_HandleStatePacketCommon(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketCommon_t *packet, Uint64 timestamp)1212{1213Sint16 axis;12141215if (ctx->last_state.state.rgucButtonsAndHat[0] != packet->rgucButtonsAndHat[0]) {1216{1217Uint8 data = (packet->rgucButtonsAndHat[0] >> 4);12181219SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));1220SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));1221SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));1222SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));1223}1224{1225Uint8 data = (packet->rgucButtonsAndHat[0] & 0x0F);1226Uint8 hat;12271228switch (data) {1229case 0:1230hat = SDL_HAT_UP;1231break;1232case 1:1233hat = SDL_HAT_RIGHTUP;1234break;1235case 2:1236hat = SDL_HAT_RIGHT;1237break;1238case 3:1239hat = SDL_HAT_RIGHTDOWN;1240break;1241case 4:1242hat = SDL_HAT_DOWN;1243break;1244case 5:1245hat = SDL_HAT_LEFTDOWN;1246break;1247case 6:1248hat = SDL_HAT_LEFT;1249break;1250case 7:1251hat = SDL_HAT_LEFTUP;1252break;1253default:1254hat = SDL_HAT_CENTERED;1255break;1256}1257SDL_SendJoystickHat(timestamp, joystick, 0, hat);1258}1259}12601261if (ctx->last_state.state.rgucButtonsAndHat[1] != packet->rgucButtonsAndHat[1]) {1262Uint8 data = packet->rgucButtonsAndHat[1];12631264SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));1265SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));1266SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));1267SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));1268SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));1269SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));1270}12711272if (ctx->last_state.state.rgucButtonsAndHat[2] != packet->rgucButtonsAndHat[2]) {1273Uint8 data = packet->rgucButtonsAndHat[2];12741275SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));1276SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_TOUCHPAD, ((data & 0x02) != 0));1277SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_MICROPHONE, ((data & 0x04) != 0));1278SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_FUNCTION, ((data & 0x10) != 0));1279SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_FUNCTION, ((data & 0x20) != 0));1280SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_LEFT_PADDLE, ((data & 0x40) != 0));1281SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS5_RIGHT_PADDLE, ((data & 0x80) != 0));1282}12831284if (packet->ucTriggerLeft == 0 && (packet->rgucButtonsAndHat[1] & 0x04)) {1285axis = SDL_JOYSTICK_AXIS_MAX;1286} else {1287axis = ((int)packet->ucTriggerLeft * 257) - 32768;1288}1289SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);1290if (packet->ucTriggerRight == 0 && (packet->rgucButtonsAndHat[1] & 0x08)) {1291axis = SDL_JOYSTICK_AXIS_MAX;1292} else {1293axis = ((int)packet->ucTriggerRight * 257) - 32768;1294}1295SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);1296axis = ((int)packet->ucLeftJoystickX * 257) - 32768;1297SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1298axis = ((int)packet->ucLeftJoystickY * 257) - 32768;1299SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1300axis = ((int)packet->ucRightJoystickX * 257) - 32768;1301SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1302axis = ((int)packet->ucRightJoystickY * 257) - 32768;1303SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);13041305if (ctx->report_sensors) {1306Uint64 sensor_timestamp;1307float data[3];13081309if (ctx->use_alternate_report) {1310// 16-bit timestamp1311Uint32 delta;1312Uint16 tick = LOAD16(packet->rgucSensorTimestamp[0],1313packet->rgucSensorTimestamp[1]);1314if (ctx->last_tick < tick) {1315delta = (tick - ctx->last_tick);1316} else {1317delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);1318}1319ctx->last_tick = tick;1320ctx->sensor_ticks += delta;13211322// Sensor timestamp is in 1us units1323sensor_timestamp = SDL_US_TO_NS(ctx->sensor_ticks);1324} else {1325// 32-bit timestamp1326Uint32 delta;1327Uint32 tick = LOAD32(packet->rgucSensorTimestamp[0],1328packet->rgucSensorTimestamp[1],1329packet->rgucSensorTimestamp[2],1330packet->rgucSensorTimestamp[3]);1331if (ctx->last_tick < tick) {1332delta = (tick - ctx->last_tick);1333} else {1334delta = (SDL_MAX_UINT32 - ctx->last_tick + tick + 1);1335}1336ctx->last_tick = tick;1337ctx->sensor_ticks += delta;13381339// Sensor timestamp is in 0.33us units1340sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US) / 3;1341}13421343data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));1344data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));1345data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));1346SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);13471348data[0] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));1349data[1] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));1350data[2] = HIDAPI_DriverPS5_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));1351SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);1352}1353}13541355static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacket_t *packet, Uint64 timestamp)1356{1357static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 19201358static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 10701359bool touchpad_down;1360int touchpad_x, touchpad_y;13611362if (ctx->report_touchpad) {1363touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);1364touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);1365touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);1366SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);13671368touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);1369touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);1370touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);1371SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);1372}13731374if (ctx->report_battery) {1375SDL_PowerState state;1376int percent;1377Uint8 status = (packet->ucBatteryLevel >> 4) & 0x0F;1378Uint8 level = (packet->ucBatteryLevel & 0x0F);13791380switch (status) {1381case 0:1382state = SDL_POWERSTATE_ON_BATTERY;1383percent = SDL_min(level * 10 + 5, 100);1384break;1385case 1:1386state = SDL_POWERSTATE_CHARGING;1387percent = SDL_min(level * 10 + 5, 100);1388break;1389case 2:1390state = SDL_POWERSTATE_CHARGED;1391percent = 100;1392break;1393default:1394state = SDL_POWERSTATE_UNKNOWN;1395percent = 0;1396break;1397}1398SDL_SendJoystickPowerInfo(joystick, state, percent);1399}14001401HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);14021403SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));1404}14051406static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS5_Context *ctx, PS5StatePacketAlt_t *packet, Uint64 timestamp)1407{1408static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 19201409static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 10701410bool touchpad_down;1411int touchpad_x, touchpad_y;14121413if (ctx->report_touchpad) {1414touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);1415touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);1416touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);1417SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);14181419touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);1420touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);1421touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);1422SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);1423}14241425HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp);14261427SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));1428}14291430static bool VerifyCRC(Uint8 *data, int size)1431{1432Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation1433Uint32 unCRC, unPacketCRC;1434Uint8 *packetCRC = data + size - sizeof(unPacketCRC);1435unCRC = SDL_crc32(0, &ubHdr, 1);1436unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));14371438unPacketCRC = LOAD32(packetCRC[0],1439packetCRC[1],1440packetCRC[2],1441packetCRC[3]);1442return (unCRC == unPacketCRC);1443}14441445static bool HIDAPI_DriverPS5_IsPacketValid(SDL_DriverPS5_Context *ctx, Uint8 *data, int size)1446{1447switch (data[0]) {1448case k_EPS5ReportIdState:1449if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS5StatePacketAlt_t))) {1450// The report timestamp doesn't change when the controller isn't connected1451PS5StatePacketAlt_t *packet = (PS5StatePacketAlt_t *)&data[1];1452if (SDL_memcmp(packet->rgucPacketSequence, ctx->last_state.state.rgucPacketSequence, sizeof(packet->rgucPacketSequence)) == 0) {1453return false;1454}1455if (ctx->last_state.alt_state.rgucAccelX[0] == 0 && ctx->last_state.alt_state.rgucAccelX[1] == 0 &&1456ctx->last_state.alt_state.rgucAccelY[0] == 0 && ctx->last_state.alt_state.rgucAccelY[1] == 0 &&1457ctx->last_state.alt_state.rgucAccelZ[0] == 0 && ctx->last_state.alt_state.rgucAccelZ[1] == 0) {1458// We don't have any state to compare yet, go ahead and copy it1459SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS5StatePacketAlt_t));1460return false;1461}1462}1463return true;14641465case k_EPS5ReportIdBluetoothState:1466if (VerifyCRC(data, size)) {1467return true;1468}1469break;1470default:1471break;1472}1473return false;1474}14751476static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)1477{1478SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;1479SDL_Joystick *joystick = NULL;1480Uint8 data[USB_PACKET_LENGTH * 2];1481int size;1482int packet_count = 0;1483Uint64 now = SDL_GetTicks();14841485if (device->num_joysticks > 0) {1486joystick = SDL_GetJoystickFromID(device->joysticks[0]);1487}14881489while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {1490Uint64 timestamp = SDL_GetTicksNS();14911492#ifdef DEBUG_PS5_PROTOCOL1493HIDAPI_DumpPacket("PS5 packet: size = %d", data, size);1494#endif1495if (!HIDAPI_DriverPS5_IsPacketValid(ctx, data, size)) {1496continue;1497}14981499++packet_count;1500ctx->last_packet = now;15011502if (!joystick) {1503continue;1504}15051506switch (data[0]) {1507case k_EPS5ReportIdState:1508if (size == 10 || size == 78) {1509HIDAPI_DriverPS5_HandleSimpleStatePacket(joystick, device->dev, ctx, (PS5SimpleStatePacket_t *)&data[1], timestamp);1510} else {1511if (ctx->use_alternate_report) {1512HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[1], timestamp);1513} else {1514HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[1], timestamp);1515}1516}1517break;1518case k_EPS5ReportIdBluetoothState:1519// This is the extended report, we can enable effects now in auto mode1520HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx);15211522if (ctx->use_alternate_report) {1523HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2], timestamp);1524} else {1525HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2], timestamp);1526}1527if (ctx->led_reset_state == k_EDS5LEDResetStatePending) {1528HIDAPI_DriverPS5_CheckPendingLEDReset(ctx);1529}1530break;1531default:1532#ifdef DEBUG_JOYSTICK1533SDL_Log("Unknown PS5 packet: 0x%.2x", data[0]);1534#endif1535break;1536}1537}15381539if (device->is_bluetooth) {1540if (packet_count == 0) {1541// Check to see if it looks like the device disconnected1542if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {1543// Send an empty output report to tickle the Bluetooth stack1544HIDAPI_DriverPS5_TickleBluetooth(device);1545ctx->last_packet = now;1546}1547} else {1548// Reconnect the Bluetooth device once the USB device is gone1549if (device->num_joysticks == 0 &&1550!HIDAPI_HasConnectedUSBDevice(device->serial)) {1551HIDAPI_JoystickConnected(device, NULL);1552}1553}1554}15551556if (ctx->is_nacon_dongle) {1557if (packet_count == 0) {1558if (device->num_joysticks > 0) {1559// Check to see if it looks like the device disconnected1560if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {1561HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1562}1563}1564} else {1565if (device->num_joysticks == 0) {1566HIDAPI_JoystickConnected(device, NULL);1567}1568}1569}15701571if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {1572// Read error, device is disconnected1573HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1574}1575return (size >= 0);1576}15771578static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1579{1580SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;15811582SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,1583SDL_PS5EnhancedReportsChanged, ctx);15841585SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,1586SDL_PS5PlayerLEDHintChanged, ctx);15871588ctx->joystick = NULL;15891590ctx->report_sensors = false;1591ctx->enhanced_mode = false;1592ctx->enhanced_mode_available = false;1593}15941595static void HIDAPI_DriverPS5_FreeDevice(SDL_HIDAPI_Device *device)1596{1597}15981599SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 = {1600SDL_HINT_JOYSTICK_HIDAPI_PS5,1601true,1602HIDAPI_DriverPS5_RegisterHints,1603HIDAPI_DriverPS5_UnregisterHints,1604HIDAPI_DriverPS5_IsEnabled,1605HIDAPI_DriverPS5_IsSupportedDevice,1606HIDAPI_DriverPS5_InitDevice,1607HIDAPI_DriverPS5_GetDevicePlayerIndex,1608HIDAPI_DriverPS5_SetDevicePlayerIndex,1609HIDAPI_DriverPS5_UpdateDevice,1610HIDAPI_DriverPS5_OpenJoystick,1611HIDAPI_DriverPS5_RumbleJoystick,1612HIDAPI_DriverPS5_RumbleJoystickTriggers,1613HIDAPI_DriverPS5_GetJoystickCapabilities,1614HIDAPI_DriverPS5_SetJoystickLED,1615HIDAPI_DriverPS5_SendJoystickEffect,1616HIDAPI_DriverPS5_SetJoystickSensorsEnabled,1617HIDAPI_DriverPS5_CloseJoystick,1618HIDAPI_DriverPS5_FreeDevice,1619};16201621#endif // SDL_JOYSTICK_HIDAPI_PS516221623#endif // SDL_JOYSTICK_HIDAPI162416251626