Path: blob/master/thirdparty/sdl/joystick/hidapi/SDL_hidapi_ps4.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/* This driver supports both simplified reports and the extended input reports enabled by Steam.21Code and logic contributed by Valve Corporation under the SDL zlib license.22*/23#include "SDL_internal.h"2425#ifdef SDL_JOYSTICK_HIDAPI2627#include "../../SDL_hints_c.h"28#include "../SDL_sysjoystick.h"29#include "SDL_hidapijoystick_c.h"30#include "SDL_hidapi_rumble.h"3132#ifdef SDL_JOYSTICK_HIDAPI_PS43334// Define this if you want to log all packets from the controller35#if 036#define DEBUG_PS4_PROTOCOL37#endif3839// Define this if you want to log calibration data40#if 041#define DEBUG_PS4_CALIBRATION42#endif4344#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 5004546#define LOAD16(A, B) (Sint16)((Uint16)(A) | (((Uint16)(B)) << 8))47#define LOAD32(A, B, C, D) ((((Uint32)(A)) << 0) | \48(((Uint32)(B)) << 8) | \49(((Uint32)(C)) << 16) | \50(((Uint32)(D)) << 24))5152enum53{54SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD = 1155};5657typedef enum58{59k_EPS4ReportIdUsbState = 1,60k_EPS4ReportIdUsbEffects = 5,61k_EPS4ReportIdBluetoothState1 = 17,62k_EPS4ReportIdBluetoothState2 = 18,63k_EPS4ReportIdBluetoothState3 = 19,64k_EPS4ReportIdBluetoothState4 = 20,65k_EPS4ReportIdBluetoothState5 = 21,66k_EPS4ReportIdBluetoothState6 = 22,67k_EPS4ReportIdBluetoothState7 = 23,68k_EPS4ReportIdBluetoothState8 = 24,69k_EPS4ReportIdBluetoothState9 = 25,70k_EPS4ReportIdBluetoothEffects = 17,71k_EPS4ReportIdDisconnectMessage = 226,72} EPS4ReportId;7374typedef enum75{76k_ePS4FeatureReportIdGyroCalibration_USB = 0x02,77k_ePS4FeatureReportIdCapabilities = 0x03,78k_ePS4FeatureReportIdGyroCalibration_BT = 0x05,79k_ePS4FeatureReportIdSerialNumber = 0x12,80} EPS4FeatureReportID;8182typedef struct83{84Uint8 ucLeftJoystickX;85Uint8 ucLeftJoystickY;86Uint8 ucRightJoystickX;87Uint8 ucRightJoystickY;88Uint8 rgucButtonsHatAndCounter[3];89Uint8 ucTriggerLeft;90Uint8 ucTriggerRight;91Uint8 rgucTimestamp[2];92Uint8 _rgucPad0[1];93Uint8 rgucGyroX[2];94Uint8 rgucGyroY[2];95Uint8 rgucGyroZ[2];96Uint8 rgucAccelX[2];97Uint8 rgucAccelY[2];98Uint8 rgucAccelZ[2];99Uint8 _rgucPad1[5];100Uint8 ucBatteryLevel;101Uint8 _rgucPad2[4];102Uint8 ucTouchpadCounter1;103Uint8 rgucTouchpadData1[3];104Uint8 ucTouchpadCounter2;105Uint8 rgucTouchpadData2[3];106} PS4StatePacket_t;107108typedef struct109{110Uint8 ucRumbleRight;111Uint8 ucRumbleLeft;112Uint8 ucLedRed;113Uint8 ucLedGreen;114Uint8 ucLedBlue;115Uint8 ucLedDelayOn;116Uint8 ucLedDelayOff;117Uint8 _rgucPad0[8];118Uint8 ucVolumeLeft;119Uint8 ucVolumeRight;120Uint8 ucVolumeMic;121Uint8 ucVolumeSpeaker;122} DS4EffectsState_t;123124typedef struct125{126Sint16 bias;127float scale;128} IMUCalibrationData;129130/* Rumble hint mode:131* "0": enhanced features are never used132* "1": enhanced features are always used133* "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.134*/135typedef enum136{137PS4_ENHANCED_REPORT_HINT_OFF,138PS4_ENHANCED_REPORT_HINT_ON,139PS4_ENHANCED_REPORT_HINT_AUTO140} HIDAPI_PS4_EnhancedReportHint;141142typedef struct143{144SDL_HIDAPI_Device *device;145SDL_Joystick *joystick;146bool is_dongle;147bool is_nacon_dongle;148bool official_controller;149bool sensors_supported;150bool lightbar_supported;151bool vibration_supported;152bool touchpad_supported;153bool effects_supported;154HIDAPI_PS4_EnhancedReportHint enhanced_report_hint;155bool enhanced_reports;156bool enhanced_mode;157bool enhanced_mode_available;158Uint8 report_interval;159bool report_sensors;160bool report_touchpad;161bool report_battery;162bool hardware_calibration;163IMUCalibrationData calibration[6];164Uint64 last_packet;165int player_index;166Uint8 rumble_left;167Uint8 rumble_right;168bool color_set;169Uint8 led_red;170Uint8 led_green;171Uint8 led_blue;172Uint16 gyro_numerator;173Uint16 gyro_denominator;174Uint16 accel_numerator;175Uint16 accel_denominator;176Uint64 sensor_ticks;177Uint16 last_tick;178Uint16 valid_crc_packets; // wrapping counter179PS4StatePacket_t last_state;180} SDL_DriverPS4_Context;181182static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage);183184static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata)185{186SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata);187}188189static void HIDAPI_DriverPS4_UnregisterHints(SDL_HintCallback callback, void *userdata)190{191SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4, callback, userdata);192}193194static bool HIDAPI_DriverPS4_IsEnabled(void)195{196return SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4, SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPI_DEFAULT));197}198199static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report, size_t length)200{201SDL_memset(report, 0, length);202report[0] = report_id;203return SDL_hid_get_feature_report(dev, report, length);204}205206static bool HIDAPI_DriverPS4_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)207{208Uint8 data[USB_PACKET_LENGTH];209int size;210211if (type == SDL_GAMEPAD_TYPE_PS4) {212return true;213}214215if (HIDAPI_SupportsPlaystationDetection(vendor_id, product_id)) {216if (device && device->dev) {217size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data));218if (size == 48 && data[2] == 0x27) {219// Supported third party controller220return true;221} else {222return false;223}224} else {225// Might be supported by this driver, enumerate and find out226return true;227}228}229230return false;231}232233static void SetLedsForPlayerIndex(DS4EffectsState_t *effects, int player_index)234{235/* This list is the same as what hid-sony.c uses in the Linux kernel.236The first 4 values correspond to what the PS4 assigns.237*/238static const Uint8 colors[7][3] = {239{ 0x00, 0x00, 0x40 }, // Blue240{ 0x40, 0x00, 0x00 }, // Red241{ 0x00, 0x40, 0x00 }, // Green242{ 0x20, 0x00, 0x20 }, // Pink243{ 0x02, 0x01, 0x00 }, // Orange244{ 0x00, 0x01, 0x01 }, // Teal245{ 0x01, 0x01, 0x01 } // White246};247248if (player_index >= 0) {249player_index %= SDL_arraysize(colors);250} else {251player_index = 0;252}253254effects->ucLedRed = colors[player_index][0];255effects->ucLedGreen = colors[player_index][1];256effects->ucLedBlue = colors[player_index][2];257}258259static bool ReadWiredSerial(SDL_HIDAPI_Device *device, char *serial, size_t serial_size)260{261Uint8 data[USB_PACKET_LENGTH];262int size;263264size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));265if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) {266(void)SDL_snprintf(serial, serial_size, "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",267data[6], data[5], data[4], data[3], data[2], data[1]);268return true;269}270return false;271}272273static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)274{275SDL_DriverPS4_Context *ctx;276Uint8 data[USB_PACKET_LENGTH];277int size;278char serial[18];279SDL_JoystickType joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;280281ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx));282if (!ctx) {283return false;284}285ctx->device = device;286287ctx->gyro_numerator = 1;288ctx->gyro_denominator = 16;289ctx->accel_numerator = 1;290ctx->accel_denominator = 8192;291292device->context = ctx;293294if (device->serial && SDL_strlen(device->serial) == 12) {295int i, j;296297j = -1;298for (i = 0; i < 12; i += 2) {299j += 1;300SDL_memmove(&serial[j], &device->serial[i], 2);301j += 2;302serial[j] = '-';303}304serial[j] = '\0';305} else {306serial[0] = '\0';307}308309// Check for type of connection310ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE);311if (ctx->is_dongle) {312ReadWiredSerial(device, serial, sizeof(serial));313ctx->enhanced_reports = true;314} else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {315ctx->enhanced_reports = true;316317} else if (device->vendor_id == USB_VENDOR_SONY) {318if (device->is_bluetooth) {319// Read a report to see if we're in enhanced mode320size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16);321#ifdef DEBUG_PS4_PROTOCOL322if (size > 0) {323HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size);324} else {325SDL_Log("PS4 first packet: size = %d", size);326}327#endif328if (size > 0 &&329data[0] >= k_EPS4ReportIdBluetoothState1 &&330data[0] <= k_EPS4ReportIdBluetoothState9) {331ctx->enhanced_reports = true;332}333} else {334ReadWiredSerial(device, serial, sizeof(serial));335ctx->enhanced_reports = true;336}337} else {338// Third party controllers appear to all be wired339ctx->enhanced_reports = true;340}341342if (device->vendor_id == USB_VENDOR_SONY) {343ctx->official_controller = true;344ctx->sensors_supported = true;345ctx->lightbar_supported = true;346ctx->vibration_supported = true;347ctx->touchpad_supported = true;348} else {349// Third party controller capability request350size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdCapabilities, data, sizeof(data));351// Get the device capabilities352if (size == 48 && data[2] == 0x27) {353Uint8 capabilities = data[4];354Uint8 device_type = data[5];355Uint16 gyro_numerator = LOAD16(data[10], data[11]);356Uint16 gyro_denominator = LOAD16(data[12], data[13]);357Uint16 accel_numerator = LOAD16(data[14], data[15]);358Uint16 accel_denominator = LOAD16(data[16], data[17]);359360#ifdef DEBUG_PS4_PROTOCOL361HIDAPI_DumpPacket("PS4 capabilities: size = %d", data, size);362#endif363if (capabilities & 0x02) {364ctx->sensors_supported = true;365}366if (capabilities & 0x04) {367ctx->lightbar_supported = true;368}369if (capabilities & 0x08) {370ctx->vibration_supported = true;371}372if (capabilities & 0x40) {373ctx->touchpad_supported = true;374}375376switch (device_type) {377case 0x00:378joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;379break;380case 0x01:381joystick_type = SDL_JOYSTICK_TYPE_GUITAR;382break;383case 0x02:384joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;385break;386case 0x04:387joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;388break;389case 0x06:390joystick_type = SDL_JOYSTICK_TYPE_WHEEL;391break;392case 0x07:393joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;394break;395case 0x08:396joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;397break;398default:399joystick_type = SDL_JOYSTICK_TYPE_UNKNOWN;400break;401}402403if (gyro_numerator && gyro_denominator) {404ctx->gyro_numerator = gyro_numerator;405ctx->gyro_denominator = gyro_denominator;406}407if (accel_numerator && accel_denominator) {408ctx->accel_numerator = accel_numerator;409ctx->accel_denominator = accel_denominator;410}411} else if (device->vendor_id == USB_VENDOR_RAZER) {412// The Razer Raiju doesn't respond to the detection protocol, but has a touchpad and vibration413ctx->vibration_supported = true;414ctx->touchpad_supported = true;415}416}417ctx->effects_supported = (ctx->lightbar_supported || ctx->vibration_supported);418419if (device->vendor_id == USB_VENDOR_NACON_ALT &&420device->product_id == USB_PRODUCT_NACON_REVOLUTION_5_PRO_PS4_WIRELESS) {421ctx->is_nacon_dongle = true;422}423424if (device->vendor_id == USB_VENDOR_PDP &&425(device->product_id == USB_PRODUCT_VICTRIX_FS_PRO ||426device->product_id == USB_PRODUCT_VICTRIX_FS_PRO_V2)) {427/* The Victrix FS Pro V2 reports that it has lightbar support,428* but it doesn't respond to the effects packet, and will hang429* on reboot if we send it.430*/431ctx->effects_supported = false;432}433434device->joystick_type = joystick_type;435device->type = SDL_GAMEPAD_TYPE_PS4;436if (ctx->official_controller) {437HIDAPI_SetDeviceName(device, "PS4 Controller");438}439HIDAPI_SetDeviceSerial(device, serial);440441// Prefer the USB device over the Bluetooth device442if (device->is_bluetooth) {443if (HIDAPI_HasConnectedUSBDevice(device->serial)) {444return true;445}446} else {447HIDAPI_DisconnectBluetoothDevice(device->serial);448}449if ((ctx->is_dongle || ctx->is_nacon_dongle) && serial[0] == '\0') {450// Not yet connected451return true;452}453return HIDAPI_JoystickConnected(device, NULL);454}455456static int HIDAPI_DriverPS4_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id)457{458return -1;459}460461static bool HIDAPI_DriverPS4_LoadOfficialCalibrationData(SDL_HIDAPI_Device *device)462{463SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;464int i, tries, size;465bool have_data = false;466Uint8 data[USB_PACKET_LENGTH];467468if (!ctx->official_controller) {469#ifdef DEBUG_PS4_CALIBRATION470SDL_Log("Not an official controller, ignoring calibration");471#endif472return false;473}474475for (tries = 0; tries < 5; ++tries) {476// For Bluetooth controllers, this report switches them into advanced report mode477size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_USB, data, sizeof(data));478if (size < 35) {479#ifdef DEBUG_PS4_CALIBRATION480SDL_Log("Short read of calibration data: %d, ignoring calibration", size);481#endif482return false;483}484485if (device->is_bluetooth) {486size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_BT, data, sizeof(data));487if (size < 35) {488#ifdef DEBUG_PS4_CALIBRATION489SDL_Log("Short read of calibration data: %d, ignoring calibration", size);490#endif491return false;492}493}494495// In some cases this report returns all zeros. Usually immediately after connection with the PS4 Dongle496for (i = 0; i < size; ++i) {497if (data[i]) {498have_data = true;499break;500}501}502if (have_data) {503break;504}505506SDL_Delay(2);507}508509if (have_data) {510Sint16 sGyroPitchBias, sGyroYawBias, sGyroRollBias;511Sint16 sGyroPitchPlus, sGyroPitchMinus;512Sint16 sGyroYawPlus, sGyroYawMinus;513Sint16 sGyroRollPlus, sGyroRollMinus;514Sint16 sGyroSpeedPlus, sGyroSpeedMinus;515516Sint16 sAccXPlus, sAccXMinus;517Sint16 sAccYPlus, sAccYMinus;518Sint16 sAccZPlus, sAccZMinus;519520float flNumerator;521float flDenominator;522Sint16 sRange2g;523524#ifdef DEBUG_PS4_CALIBRATION525HIDAPI_DumpPacket("PS4 calibration packet: size = %d", data, size);526#endif527528sGyroPitchBias = LOAD16(data[1], data[2]);529sGyroYawBias = LOAD16(data[3], data[4]);530sGyroRollBias = LOAD16(data[5], data[6]);531532if (device->is_bluetooth || ctx->is_dongle) {533sGyroPitchPlus = LOAD16(data[7], data[8]);534sGyroYawPlus = LOAD16(data[9], data[10]);535sGyroRollPlus = LOAD16(data[11], data[12]);536sGyroPitchMinus = LOAD16(data[13], data[14]);537sGyroYawMinus = LOAD16(data[15], data[16]);538sGyroRollMinus = LOAD16(data[17], data[18]);539} else {540sGyroPitchPlus = LOAD16(data[7], data[8]);541sGyroPitchMinus = LOAD16(data[9], data[10]);542sGyroYawPlus = LOAD16(data[11], data[12]);543sGyroYawMinus = LOAD16(data[13], data[14]);544sGyroRollPlus = LOAD16(data[15], data[16]);545sGyroRollMinus = LOAD16(data[17], data[18]);546}547548sGyroSpeedPlus = LOAD16(data[19], data[20]);549sGyroSpeedMinus = LOAD16(data[21], data[22]);550551sAccXPlus = LOAD16(data[23], data[24]);552sAccXMinus = LOAD16(data[25], data[26]);553sAccYPlus = LOAD16(data[27], data[28]);554sAccYMinus = LOAD16(data[29], data[30]);555sAccZPlus = LOAD16(data[31], data[32]);556sAccZMinus = LOAD16(data[33], data[34]);557558flNumerator = (float)(sGyroSpeedPlus + sGyroSpeedMinus) * ctx->gyro_denominator / ctx->gyro_numerator;559flDenominator = (float)(SDL_abs(sGyroPitchPlus - sGyroPitchBias) + SDL_abs(sGyroPitchMinus - sGyroPitchBias));560if (flDenominator != 0.0f) {561ctx->calibration[0].bias = sGyroPitchBias;562ctx->calibration[0].scale = flNumerator / flDenominator;563}564565flDenominator = (float)(SDL_abs(sGyroYawPlus - sGyroYawBias) + SDL_abs(sGyroYawMinus - sGyroYawBias));566if (flDenominator != 0.0f) {567ctx->calibration[1].bias = sGyroYawBias;568ctx->calibration[1].scale = flNumerator / flDenominator;569}570571flDenominator = (float)(SDL_abs(sGyroRollPlus - sGyroRollBias) + SDL_abs(sGyroRollMinus - sGyroRollBias));572if (flDenominator != 0.0f) {573ctx->calibration[2].bias = sGyroRollBias;574ctx->calibration[2].scale = flNumerator / flDenominator;575}576577sRange2g = sAccXPlus - sAccXMinus;578ctx->calibration[3].bias = sAccXPlus - sRange2g / 2;579ctx->calibration[3].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;580581sRange2g = sAccYPlus - sAccYMinus;582ctx->calibration[4].bias = sAccYPlus - sRange2g / 2;583ctx->calibration[4].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;584585sRange2g = sAccZPlus - sAccZMinus;586ctx->calibration[5].bias = sAccZPlus - sRange2g / 2;587ctx->calibration[5].scale = (2.0f * ctx->accel_denominator / ctx->accel_numerator) / sRange2g;588589ctx->hardware_calibration = true;590for (i = 0; i < 6; ++i) {591#ifdef DEBUG_PS4_CALIBRATION592SDL_Log("calibration[%d] bias = %d, sensitivity = %f", i, ctx->calibration[i].bias, ctx->calibration[i].scale);593#endif594// Some controllers have a bad calibration595if (SDL_abs(ctx->calibration[i].bias) > 1024 || SDL_fabsf(1.0f - ctx->calibration[i].scale) > 0.5f) {596#ifdef DEBUG_PS4_CALIBRATION597SDL_Log("invalid calibration, ignoring");598#endif599ctx->hardware_calibration = false;600}601}602} else {603#ifdef DEBUG_PS4_CALIBRATION604SDL_Log("Calibration data not available");605#endif606}607return ctx->hardware_calibration;608}609610static void HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device)611{612SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;613int i;614615if (!HIDAPI_DriverPS4_LoadOfficialCalibrationData(device)) {616for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) {617ctx->calibration[i].bias = 0;618ctx->calibration[i].scale = 1.0f;619}620}621622// Scale the raw data to the units expected by SDL623for (i = 0; i < SDL_arraysize(ctx->calibration); ++i) {624double scale = ctx->calibration[i].scale;625626if (i < 3) {627scale *= ((double)ctx->gyro_numerator / ctx->gyro_denominator) * SDL_PI_D / 180.0;628629if (device->vendor_id == USB_VENDOR_SONY &&630device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {631// The Armor-X Pro seems to only deliver half the rotation it should632scale *= 2.0;633}634} else {635scale *= ((double)ctx->accel_numerator / ctx->accel_denominator) * SDL_STANDARD_GRAVITY;636637if (device->vendor_id == USB_VENDOR_SONY &&638device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {639/* The Armor-X Pro seems to only deliver half the acceleration it should,640* and in the opposite direction on all axes */641scale *= -2.0;642}643}644ctx->calibration[i].scale = (float)scale;645}646}647648static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, int index, Sint16 value)649{650IMUCalibrationData *calibration = &ctx->calibration[index];651652return ((float)value - calibration->bias) * calibration->scale;653}654655static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage)656{657DS4EffectsState_t effects;658659SDL_zero(effects);660661if (ctx->vibration_supported) {662effects.ucRumbleLeft = ctx->rumble_left;663effects.ucRumbleRight = ctx->rumble_right;664}665666if (ctx->lightbar_supported) {667// Populate the LED state with the appropriate color from our lookup table668if (ctx->color_set) {669effects.ucLedRed = ctx->led_red;670effects.ucLedGreen = ctx->led_green;671effects.ucLedBlue = ctx->led_blue;672} else {673SetLedsForPlayerIndex(&effects, ctx->player_index);674}675}676return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);677}678679static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)680{681SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;682683if (ctx->enhanced_reports) {684// This is just a dummy packet that should have no effect, since we don't set the CRC685Uint8 data[78];686687SDL_zeroa(data);688689data[0] = k_EPS4ReportIdBluetoothEffects;690data[1] = 0xC0; // Magic value HID + CRC691692if (SDL_HIDAPI_LockRumble()) {693SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));694}695} else {696#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it697* only sends reports when the state changes, so we can't disconnect here.698*/699// We can't even send an invalid effects packet, or it will put the controller in enhanced mode700if (device->num_joysticks > 0) {701HIDAPI_JoystickDisconnected(device, device->joysticks[0]);702}703#endif704}705}706707static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx)708{709if (ctx->enhanced_mode_available) {710return;711}712ctx->enhanced_mode_available = true;713714if (ctx->touchpad_supported) {715SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);716ctx->report_touchpad = true;717}718719if (ctx->sensors_supported) {720SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));721SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));722}723724if (ctx->official_controller) {725ctx->report_battery = true;726}727728HIDAPI_UpdateDeviceProperties(ctx->device);729}730731static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)732{733HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);734735if (!ctx->enhanced_mode) {736ctx->enhanced_mode = true;737738// Switch into enhanced report mode739HIDAPI_DriverPS4_UpdateEffects(ctx, false);740}741}742743static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint)744{745switch (enhanced_report_hint) {746case PS4_ENHANCED_REPORT_HINT_OFF:747// Nothing to do, enhanced mode is a one-way ticket748break;749case PS4_ENHANCED_REPORT_HINT_ON:750HIDAPI_DriverPS4_SetEnhancedMode(ctx);751break;752case PS4_ENHANCED_REPORT_HINT_AUTO:753HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);754break;755}756ctx->enhanced_report_hint = enhanced_report_hint;757}758759static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx)760{761ctx->enhanced_reports = true;762763if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {764HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);765}766}767768static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx)769{770if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {771HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);772}773}774775static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)776{777SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;778779if (ctx->device->is_bluetooth) {780if (hint && SDL_strcasecmp(hint, "auto") == 0) {781HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO);782} else if (SDL_GetStringBoolean(hint, true)) {783HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);784} else {785HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF);786}787} else {788HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);789}790}791792static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)793{794const int DEFAULT_REPORT_INTERVAL = 4;795SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;796int new_report_interval = DEFAULT_REPORT_INTERVAL;797798if (hint) {799int report_interval = SDL_atoi(hint);800switch (report_interval) {801case 1:802case 2:803case 4:804// Valid values805new_report_interval = report_interval;806break;807default:808break;809}810}811812if (new_report_interval != ctx->report_interval) {813ctx->report_interval = (Uint8)new_report_interval;814815HIDAPI_DriverPS4_UpdateEffects(ctx, false);816SDL_LockJoysticks();817SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));818SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));819SDL_UnlockJoysticks();820}821}822823static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)824{825SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;826827if (!ctx->joystick) {828return;829}830831ctx->player_index = player_index;832833// This will set the new LED state based on the new player index834// SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode835HIDAPI_DriverPS4_UpdateEffects(ctx, false);836}837838static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)839{840SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;841842SDL_AssertJoysticksLocked();843844ctx->joystick = joystick;845ctx->last_packet = SDL_GetTicks();846ctx->report_sensors = false;847ctx->report_touchpad = false;848ctx->rumble_left = 0;849ctx->rumble_right = 0;850ctx->color_set = false;851SDL_zero(ctx->last_state);852853// Initialize player index (needed for setting LEDs)854ctx->player_index = SDL_GetJoystickPlayerIndex(joystick);855856// Initialize the joystick capabilities857joystick->nbuttons = 11;858if (ctx->touchpad_supported) {859joystick->nbuttons += 1;860}861joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;862joystick->nhats = 1;863864SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,865SDL_PS4ReportIntervalHintChanged, ctx);866SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,867SDL_PS4EnhancedReportsChanged, ctx);868return true;869}870871static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)872{873SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;874875if (!ctx->vibration_supported) {876return SDL_Unsupported();877}878879ctx->rumble_left = (low_frequency_rumble >> 8);880ctx->rumble_right = (high_frequency_rumble >> 8);881882return HIDAPI_DriverPS4_UpdateEffects(ctx, true);883}884885static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)886{887return SDL_Unsupported();888}889890static Uint32 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)891{892SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;893Uint32 result = 0;894895if (ctx->enhanced_mode_available) {896if (ctx->lightbar_supported) {897result |= SDL_JOYSTICK_CAP_RGB_LED;898}899if (ctx->vibration_supported) {900result |= SDL_JOYSTICK_CAP_RUMBLE;901}902}903904return result;905}906907static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)908{909SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;910911if (!ctx->lightbar_supported) {912return SDL_Unsupported();913}914915ctx->color_set = true;916ctx->led_red = red;917ctx->led_green = green;918ctx->led_blue = blue;919920return HIDAPI_DriverPS4_UpdateEffects(ctx, true);921}922923static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage)924{925Uint8 data[78];926int report_size, offset;927928if (!ctx->effects_supported) {929// We shouldn't be sending packets to this controller930return SDL_Unsupported();931}932933if (!ctx->enhanced_mode) {934if (application_usage) {935HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);936}937938if (!ctx->enhanced_mode) {939// We're not in enhanced mode, effects aren't allowed940return SDL_Unsupported();941}942}943944SDL_zeroa(data);945946if (ctx->device->is_bluetooth && ctx->official_controller) {947data[0] = k_EPS4ReportIdBluetoothEffects;948data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval949data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval950951report_size = 78;952offset = 6;953} else {954data[0] = k_EPS4ReportIdUsbEffects;955data[1] = 0x07; // Magic value956957report_size = 32;958offset = 4;959}960961SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));962963if (ctx->device->is_bluetooth) {964// Bluetooth reports need a CRC at the end of the packet (at least on Linux)965Uint8 ubHdr = 0xA2; // hidp header is part of the CRC calculation966Uint32 unCRC;967unCRC = SDL_crc32(0, &ubHdr, 1);968unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));969SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));970}971972if (SDL_HIDAPI_SendRumble(ctx->device, data, report_size) != report_size) {973return SDL_SetError("Couldn't send rumble packet");974}975return true;976}977978static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)979{980SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;981982return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true);983}984985static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)986{987SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;988989HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);990991if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {992return SDL_Unsupported();993}994995if (enabled) {996HIDAPI_DriverPS4_LoadCalibrationData(device);997}998ctx->report_sensors = enabled;9991000return true;1001}10021003static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, SDL_DriverPS4_Context *ctx, PS4StatePacket_t *packet, int size)1004{1005static const float TOUCHPAD_SCALEX = 5.20833333e-4f; // 1.0f / 19201006static const float TOUCHPAD_SCALEY = 1.08695652e-3f; // 1.0f / 920 // This is noted as being 944 resolution, but 920 feels better1007Sint16 axis;1008bool touchpad_down;1009int touchpad_x, touchpad_y;1010Uint64 timestamp = SDL_GetTicksNS();10111012if (size > 9 && ctx->report_touchpad && ctx->enhanced_reports) {1013touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0);1014touchpad_x = packet->rgucTouchpadData1[0] | (((int)packet->rgucTouchpadData1[1] & 0x0F) << 8);1015touchpad_y = (packet->rgucTouchpadData1[1] >> 4) | ((int)packet->rgucTouchpadData1[2] << 4);1016SDL_SendJoystickTouchpad(timestamp, joystick, 0, 0, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);10171018touchpad_down = ((packet->ucTouchpadCounter2 & 0x80) == 0);1019touchpad_x = packet->rgucTouchpadData2[0] | (((int)packet->rgucTouchpadData2[1] & 0x0F) << 8);1020touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4);1021SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_down, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_down ? 1.0f : 0.0f);1022}10231024if (ctx->last_state.rgucButtonsHatAndCounter[0] != packet->rgucButtonsHatAndCounter[0]) {1025{1026Uint8 data = (packet->rgucButtonsHatAndCounter[0] >> 4);10271028SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data & 0x01) != 0));1029SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data & 0x02) != 0));1030SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data & 0x04) != 0));1031SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data & 0x08) != 0));1032}1033{1034Uint8 hat;1035Uint8 data = (packet->rgucButtonsHatAndCounter[0] & 0x0F);10361037switch (data) {1038case 0:1039hat = SDL_HAT_UP;1040break;1041case 1:1042hat = SDL_HAT_RIGHTUP;1043break;1044case 2:1045hat = SDL_HAT_RIGHT;1046break;1047case 3:1048hat = SDL_HAT_RIGHTDOWN;1049break;1050case 4:1051hat = SDL_HAT_DOWN;1052break;1053case 5:1054hat = SDL_HAT_LEFTDOWN;1055break;1056case 6:1057hat = SDL_HAT_LEFT;1058break;1059case 7:1060hat = SDL_HAT_LEFTUP;1061break;1062default:1063hat = SDL_HAT_CENTERED;1064break;1065}1066SDL_SendJoystickHat(timestamp, joystick, 0, hat);1067}1068}10691070if (ctx->last_state.rgucButtonsHatAndCounter[1] != packet->rgucButtonsHatAndCounter[1]) {1071Uint8 data = packet->rgucButtonsHatAndCounter[1];10721073SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data & 0x01) != 0));1074SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data & 0x02) != 0));1075SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data & 0x10) != 0));1076SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data & 0x20) != 0));1077SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data & 0x40) != 0));1078SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data & 0x80) != 0));1079}10801081/* Some fightsticks, ex: Victrix FS Pro will only this these digital trigger bits and not the analog values so this needs to run whenever the1082trigger is evaluated1083*/1084if (packet->rgucButtonsHatAndCounter[1] & 0x0C) {1085Uint8 data = packet->rgucButtonsHatAndCounter[1];1086packet->ucTriggerLeft = (data & 0x04) && packet->ucTriggerLeft == 0 ? 255 : packet->ucTriggerLeft;1087packet->ucTriggerRight = (data & 0x08) && packet->ucTriggerRight == 0 ? 255 : packet->ucTriggerRight;1088}10891090if (ctx->last_state.rgucButtonsHatAndCounter[2] != packet->rgucButtonsHatAndCounter[2]) {1091Uint8 data = (packet->rgucButtonsHatAndCounter[2] & 0x03);10921093SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x01) != 0));1094SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_PS4_TOUCHPAD, ((data & 0x02) != 0));1095}10961097axis = ((int)packet->ucTriggerLeft * 257) - 32768;1098SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);1099axis = ((int)packet->ucTriggerRight * 257) - 32768;1100SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);1101axis = ((int)packet->ucLeftJoystickX * 257) - 32768;1102SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);1103axis = ((int)packet->ucLeftJoystickY * 257) - 32768;1104SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);1105axis = ((int)packet->ucRightJoystickX * 257) - 32768;1106SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);1107axis = ((int)packet->ucRightJoystickY * 257) - 32768;1108SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);11091110if (size > 9 && ctx->report_battery && ctx->enhanced_reports) {1111SDL_PowerState state;1112int percent;1113Uint8 level = (packet->ucBatteryLevel & 0x0F);11141115if (packet->ucBatteryLevel & 0x10) {1116if (level <= 10) {1117state = SDL_POWERSTATE_CHARGING;1118percent = SDL_min(level * 10 + 5, 100);1119} else if (level == 11) {1120state = SDL_POWERSTATE_CHARGED;1121percent = 100;1122} else {1123state = SDL_POWERSTATE_UNKNOWN;1124percent = 0;1125}1126} else {1127state = SDL_POWERSTATE_ON_BATTERY;1128percent = SDL_min(level * 10 + 5, 100);1129}1130SDL_SendJoystickPowerInfo(joystick, state, percent);1131}11321133if (size > 9 && ctx->report_sensors) {1134Uint16 tick;1135Uint16 delta;1136Uint64 sensor_timestamp;1137float data[3];11381139tick = LOAD16(packet->rgucTimestamp[0], packet->rgucTimestamp[1]);1140if (ctx->last_tick < tick) {1141delta = (tick - ctx->last_tick);1142} else {1143delta = (SDL_MAX_UINT16 - ctx->last_tick + tick + 1);1144}1145ctx->sensor_ticks += delta;1146ctx->last_tick = tick;11471148// Sensor timestamp is in 5.33us units1149sensor_timestamp = (ctx->sensor_ticks * SDL_NS_PER_US * 16) / 3;11501151data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 0, LOAD16(packet->rgucGyroX[0], packet->rgucGyroX[1]));1152data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 1, LOAD16(packet->rgucGyroY[0], packet->rgucGyroY[1]));1153data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 2, LOAD16(packet->rgucGyroZ[0], packet->rgucGyroZ[1]));1154SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_GYRO, sensor_timestamp, data, 3);11551156data[0] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 3, LOAD16(packet->rgucAccelX[0], packet->rgucAccelX[1]));1157data[1] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 4, LOAD16(packet->rgucAccelY[0], packet->rgucAccelY[1]));1158data[2] = HIDAPI_DriverPS4_ApplyCalibrationData(ctx, 5, LOAD16(packet->rgucAccelZ[0], packet->rgucAccelZ[1]));1159SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3);1160}11611162SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state));1163}11641165static bool VerifyCRC(Uint8 *data, int size)1166{1167Uint8 ubHdr = 0xA1; // hidp header is part of the CRC calculation1168Uint32 unCRC, unPacketCRC;1169Uint8 *packetCRC = data + size - sizeof(unPacketCRC);1170unCRC = SDL_crc32(0, &ubHdr, 1);1171unCRC = SDL_crc32(unCRC, data, (size_t)(size - sizeof(unCRC)));11721173unPacketCRC = LOAD32(packetCRC[0],1174packetCRC[1],1175packetCRC[2],1176packetCRC[3]);1177return (unCRC == unPacketCRC);1178}11791180static bool HIDAPI_DriverPS4_IsPacketValid(SDL_DriverPS4_Context *ctx, Uint8 *data, int size)1181{1182switch (data[0]) {1183case k_EPS4ReportIdUsbState:1184if (size == 10) {1185// This is non-enhanced mode, this packet is fine1186return true;1187}11881189if (ctx->is_nacon_dongle && size >= (1 + sizeof(PS4StatePacket_t))) {1190// The report timestamp doesn't change when the controller isn't connected1191PS4StatePacket_t *packet = (PS4StatePacket_t *)&data[1];1192if (SDL_memcmp(packet->rgucTimestamp, ctx->last_state.rgucTimestamp, sizeof(packet->rgucTimestamp)) == 0) {1193return false;1194}1195if (ctx->last_state.rgucAccelX[0] == 0 && ctx->last_state.rgucAccelX[1] == 0 &&1196ctx->last_state.rgucAccelY[0] == 0 && ctx->last_state.rgucAccelY[1] == 0 &&1197ctx->last_state.rgucAccelZ[0] == 0 && ctx->last_state.rgucAccelZ[1] == 0) {1198// We don't have any state to compare yet, go ahead and copy it1199SDL_memcpy(&ctx->last_state, &data[1], sizeof(PS4StatePacket_t));1200return false;1201}1202}12031204/* In the case of a DS4 USB dongle, bit[2] of byte 31 indicates if a DS4 is actually connected (indicated by '0').1205* For non-dongle, this bit is always 0 (connected).1206* This is usually the ID over USB, but the DS4v2 that started shipping with the PS4 Slim will also send this1207* packet over BT with a size of 1281208*/1209if (size >= 64 && !(data[31] & 0x04)) {1210return true;1211}1212break;1213case k_EPS4ReportIdBluetoothState1:1214case k_EPS4ReportIdBluetoothState2:1215case k_EPS4ReportIdBluetoothState3:1216case k_EPS4ReportIdBluetoothState4:1217case k_EPS4ReportIdBluetoothState5:1218case k_EPS4ReportIdBluetoothState6:1219case k_EPS4ReportIdBluetoothState7:1220case k_EPS4ReportIdBluetoothState8:1221case k_EPS4ReportIdBluetoothState9:1222// Bluetooth state packets have two additional bytes at the beginning, the first notes if HID data is present1223if (size >= 78 && (data[1] & 0x80)) {1224if (VerifyCRC(data, 78)) {1225++ctx->valid_crc_packets;1226} else {1227if (ctx->valid_crc_packets > 0) {1228--ctx->valid_crc_packets;1229}1230if (ctx->valid_crc_packets >= 3) {1231// We're generally getting valid CRC, but failed one1232return false;1233}1234}1235return true;1236}1237break;1238default:1239break;1240}1241return false;1242}12431244static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)1245{1246SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;1247SDL_Joystick *joystick = NULL;1248Uint8 data[USB_PACKET_LENGTH * 2];1249int size;1250int packet_count = 0;1251Uint64 now = SDL_GetTicks();12521253if (device->num_joysticks > 0) {1254joystick = SDL_GetJoystickFromID(device->joysticks[0]);1255}12561257while ((size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 0)) > 0) {1258#ifdef DEBUG_PS4_PROTOCOL1259HIDAPI_DumpPacket("PS4 packet: size = %d", data, size);1260#endif1261if (!HIDAPI_DriverPS4_IsPacketValid(ctx, data, size)) {1262continue;1263}12641265++packet_count;1266ctx->last_packet = now;12671268if (!joystick) {1269continue;1270}12711272switch (data[0]) {1273case k_EPS4ReportIdUsbState:1274HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[1], size - 1);1275break;1276case k_EPS4ReportIdBluetoothState1:1277case k_EPS4ReportIdBluetoothState2:1278case k_EPS4ReportIdBluetoothState3:1279case k_EPS4ReportIdBluetoothState4:1280case k_EPS4ReportIdBluetoothState5:1281case k_EPS4ReportIdBluetoothState6:1282case k_EPS4ReportIdBluetoothState7:1283case k_EPS4ReportIdBluetoothState8:1284case k_EPS4ReportIdBluetoothState9:1285// This is the extended report, we can enable effects now in auto mode1286HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx);12871288// Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present1289HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3);1290break;1291default:1292#ifdef DEBUG_JOYSTICK1293SDL_Log("Unknown PS4 packet: 0x%.2x", data[0]);1294#endif1295break;1296}1297}12981299if (device->is_bluetooth) {1300if (packet_count == 0) {1301// Check to see if it looks like the device disconnected1302if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {1303// Send an empty output report to tickle the Bluetooth stack1304HIDAPI_DriverPS4_TickleBluetooth(device);1305ctx->last_packet = now;1306}1307} else {1308// Reconnect the Bluetooth device once the USB device is gone1309if (device->num_joysticks == 0 &&1310!HIDAPI_HasConnectedUSBDevice(device->serial)) {1311HIDAPI_JoystickConnected(device, NULL);1312}1313}1314}13151316if (ctx->is_dongle || ctx->is_nacon_dongle) {1317if (packet_count == 0) {1318if (device->num_joysticks > 0) {1319// Check to see if it looks like the device disconnected1320if (now >= (ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) {1321HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1322}1323}1324} else {1325if (device->num_joysticks == 0) {1326char serial[18];1327size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdSerialNumber, data, sizeof(data));1328if (size >= 7 && (data[1] || data[2] || data[3] || data[4] || data[5] || data[6])) {1329(void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",1330data[6], data[5], data[4], data[3], data[2], data[1]);1331HIDAPI_SetDeviceSerial(device, serial);1332}1333HIDAPI_JoystickConnected(device, NULL);1334}1335}1336}13371338if (packet_count == 0 && size < 0 && device->num_joysticks > 0) {1339// Read error, device is disconnected1340HIDAPI_JoystickDisconnected(device, device->joysticks[0]);1341}1342return (size >= 0);1343}13441345static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)1346{1347SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;13481349SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,1350SDL_PS4ReportIntervalHintChanged, ctx);1351SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,1352SDL_PS4EnhancedReportsChanged, ctx);13531354ctx->joystick = NULL;13551356ctx->report_sensors = false;1357ctx->enhanced_mode = false;1358ctx->enhanced_mode_available = false;1359}13601361static void HIDAPI_DriverPS4_FreeDevice(SDL_HIDAPI_Device *device)1362{1363}13641365SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 = {1366SDL_HINT_JOYSTICK_HIDAPI_PS4,1367true,1368HIDAPI_DriverPS4_RegisterHints,1369HIDAPI_DriverPS4_UnregisterHints,1370HIDAPI_DriverPS4_IsEnabled,1371HIDAPI_DriverPS4_IsSupportedDevice,1372HIDAPI_DriverPS4_InitDevice,1373HIDAPI_DriverPS4_GetDevicePlayerIndex,1374HIDAPI_DriverPS4_SetDevicePlayerIndex,1375HIDAPI_DriverPS4_UpdateDevice,1376HIDAPI_DriverPS4_OpenJoystick,1377HIDAPI_DriverPS4_RumbleJoystick,1378HIDAPI_DriverPS4_RumbleJoystickTriggers,1379HIDAPI_DriverPS4_GetJoystickCapabilities,1380HIDAPI_DriverPS4_SetJoystickLED,1381HIDAPI_DriverPS4_SendJoystickEffect,1382HIDAPI_DriverPS4_SetJoystickSensorsEnabled,1383HIDAPI_DriverPS4_CloseJoystick,1384HIDAPI_DriverPS4_FreeDevice,1385};13861387#endif // SDL_JOYSTICK_HIDAPI_PS413881389#endif // SDL_JOYSTICK_HIDAPI139013911392