joystick: Fix conflicting rumble and LED on Sony PS4 gamepads

This commit is contained in:
Cameron Gutman 2026-05-30 13:03:42 -05:00 committed by Sam Lantinga
parent e1aaebff87
commit 189877b1d3

View file

@ -117,6 +117,14 @@ typedef struct
Uint8 ucVolumeSpeaker;
} DS4EffectsState_t;
// These enum values match with the validity flags in the PS4 output report
typedef enum
{
k_EPS4EffectRumble = (1 << 0),
k_EPS4EffectLED = (1 << 1),
k_EPS4EffectLEDBlink = (1 << 2),
} EPS4Effect;
typedef struct
{
Sint16 bias;
@ -178,7 +186,7 @@ typedef struct
PS4StatePacket_t last_state;
} SDL_DriverPS4_Context;
static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage);
static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, EPS4Effect mask, bool application_usage);
static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata)
{
@ -661,7 +669,7 @@ static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, i
return ((float)value - calibration->bias) * calibration->scale;
}
static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool application_usage)
static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, EPS4Effect mask, bool application_usage)
{
DS4EffectsState_t effects;
@ -682,7 +690,7 @@ static bool HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, bool appl
SetLedsForPlayerIndex(&effects, ctx->player_index);
}
}
return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), mask, application_usage);
}
static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
@ -749,7 +757,7 @@ static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
ctx->enhanced_mode = true;
// Switch into enhanced report mode
HIDAPI_DriverPS4_UpdateEffects(ctx, false);
HIDAPI_DriverPS4_UpdateEffects(ctx, 0, false);
}
}
@ -825,7 +833,7 @@ static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char
if (new_report_interval != ctx->report_interval) {
ctx->report_interval = (Uint8)new_report_interval;
HIDAPI_DriverPS4_UpdateEffects(ctx, false);
HIDAPI_DriverPS4_UpdateEffects(ctx, 0, false);
SDL_LockJoysticks();
SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_GYRO, (float)(1000 / ctx->report_interval));
SDL_PrivateJoystickSensorRate(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval));
@ -845,7 +853,7 @@ static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL
// This will set the new LED state based on the new player index
// SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode
HIDAPI_DriverPS4_UpdateEffects(ctx, false);
HIDAPI_DriverPS4_UpdateEffects(ctx, k_EPS4EffectLED, false);
}
static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
@ -892,7 +900,7 @@ static bool HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joyst
ctx->rumble_left = (low_frequency_rumble >> 8);
ctx->rumble_right = (high_frequency_rumble >> 8);
return HIDAPI_DriverPS4_UpdateEffects(ctx, true);
return HIDAPI_DriverPS4_UpdateEffects(ctx, k_EPS4EffectRumble, true);
}
static bool HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@ -930,10 +938,10 @@ static bool HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joyst
ctx->led_green = green;
ctx->led_blue = blue;
return HIDAPI_DriverPS4_UpdateEffects(ctx, true);
return HIDAPI_DriverPS4_UpdateEffects(ctx, k_EPS4EffectLED, true);
}
static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, bool application_usage)
static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, EPS4Effect mask, bool application_usage)
{
Uint8 data[78];
int report_size, offset;
@ -959,13 +967,21 @@ static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *c
if (ctx->device->is_bluetooth && ctx->official_controller) {
data[0] = k_EPS4ReportIdBluetoothEffects;
data[1] = 0xC0 | ctx->report_interval; // Magic value HID + CRC, also sets update interval
data[3] = 0x03; // 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval
// Some third-party PS4 gamepads expect to receive both rumble and LED together,
// so we will only send them separately to official Sony gamepads. Unfortunately,
// this means that we'll fight with other apps that might be controlling one or
// the other separately for third-party PS4 gamepads. We can add a whitelist for
// compatible third-party gamepads later if we want.
data[3] = (Uint8)(ctx->official_controller ? mask : (k_EPS4EffectRumble | k_EPS4EffectLED));
report_size = 78;
offset = 6;
} else {
data[0] = k_EPS4ReportIdUsbEffects;
data[1] = 0x07; // Magic value
// FIXME: Should we send a consistent default effect mask between BT and USB?
data[1] = (Uint8)(ctx->official_controller ? mask : (k_EPS4EffectRumble | k_EPS4EffectLED | k_EPS4EffectLEDBlink));
report_size = 32;
offset = 4;
@ -991,8 +1007,18 @@ static bool HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *c
static bool HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
{
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
EPS4Effect mask;
return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, true);
// Unlike the PS5 driver, the PS4 driver does not expect the validity bits as part of
// the provided effect data. The default values we provide also diverge for different
// gamepads and connection mediums. We should probably clean this up eventually.
if (ctx->device->is_bluetooth && ctx->official_controller) {
mask = k_EPS4EffectRumble | k_EPS4EffectLED;
} else {
mask = k_EPS4EffectRumble | k_EPS4EffectLED | k_EPS4EffectLEDBlink;
}
return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, mask, true);
}
static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled)