Make sure we don't touch the controller effects state when we're in auto mode for PS4/PS5 controllers.

main
Sam Lantinga 2023-07-24 10:17:02 -07:00
parent 8adab0b88a
commit 3c8f4dca1d
2 changed files with 210 additions and 104 deletions

View File

@ -123,6 +123,20 @@ typedef struct
float scale; float scale;
} IMUCalibrationData; } IMUCalibrationData;
/* Rumble hint mode:
* default: enhanced features are available if the controller is using enhanced reports
* "0": enhanced features are never used
* "1": enhanced features are always used
* "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
*/
typedef enum
{
PS4_RUMBLE_HINT_DEFAULT,
PS4_RUMBLE_HINT_OFF,
PS4_RUMBLE_HINT_ON,
PS4_RUMBLE_HINT_AUTO
} SDL_PS4_RumbleHintMode;
typedef struct typedef struct
{ {
SDL_HIDAPI_Device *device; SDL_HIDAPI_Device *device;
@ -134,6 +148,8 @@ typedef struct
SDL_bool vibration_supported; SDL_bool vibration_supported;
SDL_bool touchpad_supported; SDL_bool touchpad_supported;
SDL_bool effects_supported; SDL_bool effects_supported;
SDL_PS4_RumbleHintMode rumble_hint;
SDL_bool enhanced_reports;
SDL_bool enhanced_mode; SDL_bool enhanced_mode;
SDL_bool enhanced_mode_available; SDL_bool enhanced_mode_available;
SDL_bool report_sensors; SDL_bool report_sensors;
@ -159,7 +175,7 @@ typedef struct
PS4StatePacket_t last_state; PS4StatePacket_t last_state;
} SDL_DriverPS4_Context; } SDL_DriverPS4_Context;
static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size); static int HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, SDL_bool application_usage);
static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata) static void HIDAPI_DriverPS4_RegisterHints(SDL_HintCallback callback, void *userdata)
{ {
@ -291,9 +307,9 @@ static SDL_bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE); ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE);
if (ctx->is_dongle) { if (ctx->is_dongle) {
ReadWiredSerial(device, serial, sizeof(serial)); ReadWiredSerial(device, serial, sizeof(serial));
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) { } else if (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_STRIKEPAD) {
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} else if (device->vendor_id == USB_VENDOR_SONY) { } else if (device->vendor_id == USB_VENDOR_SONY) {
if (device->is_bluetooth) { if (device->is_bluetooth) {
@ -309,15 +325,15 @@ static SDL_bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device)
if (size > 0 && if (size > 0 &&
data[0] >= k_EPS4ReportIdBluetoothState1 && data[0] >= k_EPS4ReportIdBluetoothState1 &&
data[0] <= k_EPS4ReportIdBluetoothState9) { data[0] <= k_EPS4ReportIdBluetoothState9) {
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} }
} else { } else {
ReadWiredSerial(device, serial, sizeof(serial)); ReadWiredSerial(device, serial, sizeof(serial));
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} }
} else { } else {
/* Third party controllers appear to all be wired */ /* Third party controllers appear to all be wired */
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} }
if (device->vendor_id == USB_VENDOR_SONY) { if (device->vendor_id == USB_VENDOR_SONY) {
@ -628,9 +644,8 @@ static float HIDAPI_DriverPS4_ApplyCalibrationData(SDL_DriverPS4_Context *ctx, i
return ((float)value - calibration->bias) * calibration->scale; return ((float)value - calibration->bias) * calibration->scale;
} }
static int HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device) static int HIDAPI_DriverPS4_UpdateEffects(SDL_DriverPS4_Context *ctx, SDL_bool application_usage)
{ {
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
DS4EffectsState_t effects; DS4EffectsState_t effects;
SDL_zero(effects); SDL_zero(effects);
@ -650,14 +665,14 @@ static int HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device)
SetLedsForPlayerIndex(&effects, ctx->player_index); SetLedsForPlayerIndex(&effects, ctx->player_index);
} }
} }
return HIDAPI_DriverPS4_SendJoystickEffect(device, ctx->joystick, &effects, sizeof(effects)); return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
} }
static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
{ {
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
if (ctx->enhanced_mode) { if (ctx->enhanced_reports) {
/* This is just a dummy packet that should have no effect, since we don't set the CRC */ /* This is just a dummy packet that should have no effect, since we don't set the CRC */
Uint8 data[78]; Uint8 data[78];
@ -712,7 +727,44 @@ static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
ctx->report_battery = SDL_TRUE; ctx->report_battery = SDL_TRUE;
} }
HIDAPI_DriverPS4_UpdateEffects(ctx->device); HIDAPI_DriverPS4_UpdateEffects(ctx, SDL_FALSE);
}
}
static void HIDAPI_DriverPS4_SetRumbleHintMode(SDL_DriverPS4_Context *ctx, SDL_PS4_RumbleHintMode rumble_hint)
{
switch (rumble_hint) {
case PS4_RUMBLE_HINT_DEFAULT:
if (ctx->enhanced_reports) {
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
}
break;
case PS4_RUMBLE_HINT_OFF:
/* Nothing to do, enhanced mode is a one-way ticket */
break;
case PS4_RUMBLE_HINT_ON:
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
break;
case PS4_RUMBLE_HINT_AUTO:
HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
break;
}
ctx->rumble_hint = rumble_hint;
}
static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx)
{
ctx->enhanced_reports = SDL_TRUE;
if (ctx->rumble_hint == PS4_RUMBLE_HINT_DEFAULT) {
HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON);
}
}
static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx)
{
if (ctx->rumble_hint == PS4_RUMBLE_HINT_AUTO) {
HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON);
} }
} }
@ -720,12 +772,14 @@ static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, c
{ {
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata; SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
if (SDL_strcasecmp(hint, "auto") == 0) { if (!hint) {
/* Mark the controller as enhanced mode capable, but wait for API calls to enable it */ HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_DEFAULT);
HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); } else if (SDL_strcasecmp(hint, "auto") == 0) {
HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_AUTO);
} else if (SDL_GetStringBoolean(hint, SDL_FALSE)) { } else if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
/* This is a one-way trip, you can't switch the controller back to simple report mode */ HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON);
HIDAPI_DriverPS4_SetEnhancedMode(ctx); } else {
HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_OFF);
} }
} }
@ -740,7 +794,8 @@ static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL
ctx->player_index = player_index; ctx->player_index = player_index;
/* This will set the new LED state based on the new player index */ /* This will set the new LED state based on the new player index */
HIDAPI_DriverPS4_UpdateEffects(device); /* SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode */
HIDAPI_DriverPS4_UpdateEffects(ctx, SDL_FALSE);
} }
static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
@ -771,14 +826,8 @@ static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
} }
if (ctx->enhanced_mode) { SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
/* Force initialization when opening the joystick */ SDL_PS4RumbleHintChanged, ctx);
ctx->enhanced_mode = SDL_FALSE;
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
} else {
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
SDL_PS4RumbleHintChanged, ctx);
}
return SDL_TRUE; return SDL_TRUE;
} }
@ -793,7 +842,7 @@ static int HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joysti
ctx->rumble_left = (low_frequency_rumble >> 8); ctx->rumble_left = (low_frequency_rumble >> 8);
ctx->rumble_right = (high_frequency_rumble >> 8); ctx->rumble_right = (high_frequency_rumble >> 8);
return HIDAPI_DriverPS4_UpdateEffects(device); return HIDAPI_DriverPS4_UpdateEffects(ctx, SDL_TRUE);
} }
static int HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) static int HIDAPI_DriverPS4_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@ -831,26 +880,25 @@ static int HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joysti
ctx->led_green = green; ctx->led_green = green;
ctx->led_blue = blue; ctx->led_blue = blue;
return HIDAPI_DriverPS4_UpdateEffects(device); return HIDAPI_DriverPS4_UpdateEffects(ctx, SDL_TRUE);
} }
static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) static int HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ctx, const void *effect, int size, SDL_bool application_usage)
{ {
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
Uint8 data[78]; Uint8 data[78];
int report_size, offset; int report_size, offset;
if (application_usage) {
HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);
}
if (!ctx->enhanced_mode_available) { if (!ctx->enhanced_mode_available) {
return SDL_Unsupported(); return SDL_Unsupported();
} }
if (!ctx->enhanced_mode) {
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
}
SDL_zeroa(data); SDL_zeroa(data);
if (device->is_bluetooth && ctx->official_controller) { if (ctx->device->is_bluetooth && ctx->official_controller) {
data[0] = k_EPS4ReportIdBluetoothEffects; data[0] = k_EPS4ReportIdBluetoothEffects;
data[1] = 0xC0 | 0x04; /* Magic value HID + CRC, also sets interval to 4ms for samples */ data[1] = 0xC0 | 0x04; /* Magic value HID + CRC, also sets interval to 4ms for samples */
data[3] = 0x03; /* 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval */ data[3] = 0x03; /* 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval */
@ -867,7 +915,7 @@ static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
if (device->is_bluetooth) { if (ctx->device->is_bluetooth) {
/* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */ /* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */
Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */ Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */
Uint32 unCRC; Uint32 unCRC;
@ -876,22 +924,30 @@ static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
} }
if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { if (SDL_HIDAPI_SendRumble(ctx->device, data, report_size) != report_size) {
return SDL_SetError("Couldn't send rumble packet"); return SDL_SetError("Couldn't send rumble packet");
} }
return 0; return 0;
} }
static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
{
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
return HIDAPI_DriverPS4_InternalSendJoystickEffect(ctx, effect, size, SDL_TRUE);
}
static int HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled) static int HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{ {
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
if (!ctx->enhanced_mode_available) { HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx);
if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {
return SDL_Unsupported(); return SDL_Unsupported();
} }
if (enabled) { if (enabled) {
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
HIDAPI_DriverPS4_LoadCalibrationData(device); HIDAPI_DriverPS4_LoadCalibrationData(device);
} }
ctx->report_sensors = enabled; ctx->report_sensors = enabled;
@ -1166,10 +1222,9 @@ static SDL_bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
case k_EPS4ReportIdBluetoothState7: case k_EPS4ReportIdBluetoothState7:
case k_EPS4ReportIdBluetoothState8: case k_EPS4ReportIdBluetoothState8:
case k_EPS4ReportIdBluetoothState9: case k_EPS4ReportIdBluetoothState9:
if (!ctx->enhanced_mode) { /* This is the extended report, we can enable effects now in default mode */
/* This is the extended report, we can enable effects now */ HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx);
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
}
/* Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present */ /* Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present */
HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3); HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t *)&data[3], size - 3);
break; break;

View File

@ -211,6 +211,20 @@ typedef struct
float sensitivity; float sensitivity;
} IMUCalibrationData; } IMUCalibrationData;
/* Rumble hint mode:
* default: enhanced features are available if the controller is using enhanced reports
* "0": enhanced features are never used
* "1": enhanced features are always used
* "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
*/
typedef enum
{
PS5_RUMBLE_HINT_DEFAULT,
PS5_RUMBLE_HINT_OFF,
PS5_RUMBLE_HINT_ON,
PS5_RUMBLE_HINT_AUTO
} SDL_PS5_RumbleHintMode;
typedef struct typedef struct
{ {
SDL_HIDAPI_Device *device; SDL_HIDAPI_Device *device;
@ -222,6 +236,8 @@ typedef struct
SDL_bool playerled_supported; SDL_bool playerled_supported;
SDL_bool touchpad_supported; SDL_bool touchpad_supported;
SDL_bool effects_supported; SDL_bool effects_supported;
SDL_PS5_RumbleHintMode rumble_hint;
SDL_bool enhanced_reports;
SDL_bool enhanced_mode; SDL_bool enhanced_mode;
SDL_bool enhanced_mode_available; SDL_bool enhanced_mode_available;
SDL_bool report_sensors; SDL_bool report_sensors;
@ -251,7 +267,7 @@ typedef struct
} last_state; } last_state;
} SDL_DriverPS5_Context; } SDL_DriverPS5_Context;
static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size); static int HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, SDL_bool application_usage);
static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata) static void HIDAPI_DriverPS5_RegisterHints(SDL_HintCallback callback, void *userdata)
{ {
@ -388,26 +404,15 @@ static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
#endif #endif
if (size == 64) { if (size == 64) {
/* Connected over USB */ /* Connected over USB */
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) { } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) {
/* Connected over Bluetooth, using enhanced reports */ /* Connected over Bluetooth, using enhanced reports */
ctx->enhanced_mode = SDL_TRUE; ctx->enhanced_reports = SDL_TRUE;
} else { } else {
/* Connected over Bluetooth, using simple reports (DirectInput enabled) */ /* Connected over Bluetooth, using simple reports (DirectInput enabled) */
const char *hint = SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE);
if (!hint) {
/* Games written prior the introduction of PS5 controller support in SDL will not be aware of
SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, but they did know SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE.
To support apps that only knew about the PS4 hint, we'll use the PS4 hint as the default.
*/
hint = SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE);
}
if (hint && SDL_strcasecmp(hint, "auto") != 0) {
ctx->enhanced_mode = SDL_GetStringBoolean(hint, SDL_FALSE);
}
} }
if (ctx->enhanced_mode) { if (ctx->enhanced_reports) {
/* Read the serial number (Bluetooth address in reverse byte order) /* Read the serial number (Bluetooth address in reverse byte order)
This will also enable enhanced reports over Bluetooth This will also enable enhanced reports over Bluetooth
*/ */
@ -635,13 +640,12 @@ static float HIDAPI_DriverPS5_ApplyCalibrationData(SDL_DriverPS5_Context *ctx, i
return result; return result;
} }
static int HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_mask) static int HIDAPI_DriverPS5_UpdateEffects(SDL_DriverPS5_Context *ctx, int effect_mask, SDL_bool application_usage)
{ {
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
DS5EffectsState_t effects; DS5EffectsState_t effects;
/* Make sure the Bluetooth connection sequence has completed before sending LED color change */ /* Make sure the Bluetooth connection sequence has completed before sending LED color change */
if (device->is_bluetooth && ctx->enhanced_mode && if (ctx->device->is_bluetooth && ctx->enhanced_reports &&
(effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) { (effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) {
if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) { if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) {
ctx->led_reset_state = k_EDS5LEDResetStatePending; ctx->led_reset_state = k_EDS5LEDResetStatePending;
@ -711,15 +715,14 @@ static int HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_
effects.ucMicLightMode = 0; /* Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse */ effects.ucMicLightMode = 0; /* Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse */
} }
return HIDAPI_DriverPS5_SendJoystickEffect(device, ctx->joystick, &effects, sizeof(effects)); return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, &effects, sizeof(effects), application_usage);
} }
static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_HIDAPI_Device *device) static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_DriverPS5_Context *ctx)
{ {
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
SDL_bool led_reset_complete = SDL_FALSE; SDL_bool led_reset_complete = SDL_FALSE;
if (ctx->sensors_supported) { if (ctx->enhanced_reports && ctx->sensors_supported) {
const PS5StatePacketCommon_t *packet = &ctx->last_state.state; const PS5StatePacketCommon_t *packet = &ctx->last_state.state;
/* Check the timer to make sure the Bluetooth connection LED animation is complete */ /* Check the timer to make sure the Bluetooth connection LED animation is complete */
@ -737,11 +740,11 @@ static void HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_HIDAPI_Device *device)
} }
if (led_reset_complete) { if (led_reset_complete) {
HIDAPI_DriverPS5_UpdateEffects(device, k_EDS5EffectLEDReset); HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLEDReset, SDL_FALSE);
ctx->led_reset_state = k_EDS5LEDResetStateComplete; ctx->led_reset_state = k_EDS5LEDResetStateComplete;
HIDAPI_DriverPS5_UpdateEffects(device, (k_EDS5EffectLED | k_EDS5EffectPadLights)); HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), SDL_FALSE);
} }
} }
@ -749,7 +752,7 @@ static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)
{ {
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
if (ctx->enhanced_mode) { if (ctx->enhanced_reports) {
/* This is just a dummy packet that should have no effect, since we don't set the CRC */ /* This is just a dummy packet that should have no effect, since we don't set the CRC */
Uint8 data[78]; Uint8 data[78];
@ -811,10 +814,47 @@ static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)
} }
/* Switch into enhanced report mode */ /* Switch into enhanced report mode */
HIDAPI_DriverPS5_UpdateEffects(ctx->device, 0); HIDAPI_DriverPS5_UpdateEffects(ctx, 0, SDL_FALSE);
/* Update the light effects */ /* Update the light effects */
HIDAPI_DriverPS5_UpdateEffects(ctx->device, (k_EDS5EffectLED | k_EDS5EffectPadLights)); HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), SDL_FALSE);
}
}
static void HIDAPI_DriverPS5_SetRumbleHintMode(SDL_DriverPS5_Context *ctx, SDL_PS5_RumbleHintMode rumble_hint)
{
switch (rumble_hint) {
case PS5_RUMBLE_HINT_DEFAULT:
if (ctx->enhanced_reports) {
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
}
break;
case PS5_RUMBLE_HINT_OFF:
/* Nothing to do, enhanced mode is a one-way ticket */
break;
case PS5_RUMBLE_HINT_ON:
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
break;
case PS5_RUMBLE_HINT_AUTO:
HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
break;
}
ctx->rumble_hint = rumble_hint;
}
static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx)
{
ctx->enhanced_reports = SDL_TRUE;
if (ctx->rumble_hint == PS5_RUMBLE_HINT_DEFAULT) {
HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON);
}
}
static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx)
{
if (ctx->rumble_hint == PS5_RUMBLE_HINT_AUTO) {
HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON);
} }
} }
@ -822,12 +862,22 @@ static void SDLCALL SDL_PS5RumbleHintChanged(void *userdata, const char *name, c
{ {
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata; SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
if (SDL_strcasecmp(hint, "auto") == 0) { if (!hint) {
/* Mark the controller as enhanced mode capable, but wait for API calls to enable it */ /* Games written prior the introduction of PS5 controller support in SDL will not be aware of
HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, but they did know SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE.
To support apps that only knew about the PS4 hint, we'll use the PS4 hint as the default.
*/
hint = SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE);
}
if (!hint) {
HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_DEFAULT);
} else if (SDL_strcasecmp(hint, "auto") == 0) {
HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_AUTO);
} else if (SDL_GetStringBoolean(hint, SDL_FALSE)) { } else if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
/* This is a one-way trip, you can't switch the controller back to simple report mode */ HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON);
HIDAPI_DriverPS5_SetEnhancedMode(ctx); } else {
HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_OFF);
} }
} }
@ -839,7 +889,7 @@ static void SDLCALL SDL_PS5PlayerLEDHintChanged(void *userdata, const char *name
if (player_lights != ctx->player_lights) { if (player_lights != ctx->player_lights) {
ctx->player_lights = player_lights; ctx->player_lights = player_lights;
HIDAPI_DriverPS5_UpdateEffects(ctx->device, k_EDS5EffectPadLights); HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectPadLights, SDL_FALSE);
} }
} }
@ -854,7 +904,8 @@ static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL
ctx->player_index = player_index; ctx->player_index = player_index;
/* This will set the new LED state based on the new player index */ /* This will set the new LED state based on the new player index */
HIDAPI_DriverPS5_UpdateEffects(device, (k_EDS5EffectLED | k_EDS5EffectPadLights)); /* SDL automatically calls this, so it doesn't count as an application action to enable enhanced mode */
HIDAPI_DriverPS5_UpdateEffects(ctx, (k_EDS5EffectLED | k_EDS5EffectPadLights), SDL_FALSE);
} }
static SDL_bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) static SDL_bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
@ -894,14 +945,8 @@ static SDL_bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
} }
joystick->firmware_version = ctx->firmware_version; joystick->firmware_version = ctx->firmware_version;
if (ctx->enhanced_mode) { SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
/* Force initialization when opening the joystick */ SDL_PS5RumbleHintChanged, ctx);
ctx->enhanced_mode = SDL_FALSE;
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
} else {
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
SDL_PS5RumbleHintChanged, ctx);
}
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
SDL_PS5PlayerLEDHintChanged, ctx); SDL_PS5PlayerLEDHintChanged, ctx);
@ -917,13 +962,13 @@ static int HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joysti
} }
if (!ctx->rumble_left && !ctx->rumble_right) { if (!ctx->rumble_left && !ctx->rumble_right) {
HIDAPI_DriverPS5_UpdateEffects(device, k_EDS5EffectRumbleStart); HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumbleStart, SDL_TRUE);
} }
ctx->rumble_left = (low_frequency_rumble >> 8); ctx->rumble_left = (low_frequency_rumble >> 8);
ctx->rumble_right = (high_frequency_rumble >> 8); ctx->rumble_right = (high_frequency_rumble >> 8);
return HIDAPI_DriverPS5_UpdateEffects(device, k_EDS5EffectRumble); return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectRumble, SDL_TRUE);
} }
static int HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble) static int HIDAPI_DriverPS5_RumbleJoystickTriggers(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
@ -961,29 +1006,28 @@ static int HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joysti
ctx->led_green = green; ctx->led_green = green;
ctx->led_blue = blue; ctx->led_blue = blue;
return HIDAPI_DriverPS5_UpdateEffects(device, k_EDS5EffectLED); return HIDAPI_DriverPS5_UpdateEffects(ctx, k_EDS5EffectLED, SDL_TRUE);
} }
static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size) static int HIDAPI_DriverPS5_InternalSendJoystickEffect(SDL_DriverPS5_Context *ctx, const void *effect, int size, SDL_bool application_usage)
{ {
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
Uint8 data[78]; Uint8 data[78];
int report_size, offset; int report_size, offset;
Uint8 *pending_data; Uint8 *pending_data;
int *pending_size; int *pending_size;
int maximum_size; int maximum_size;
if (application_usage) {
HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
}
if (!ctx->enhanced_mode_available) { if (!ctx->enhanced_mode_available) {
return SDL_Unsupported(); return SDL_Unsupported();
} }
if (!ctx->enhanced_mode) {
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
}
SDL_zeroa(data); SDL_zeroa(data);
if (device->is_bluetooth) { if (ctx->device->is_bluetooth) {
data[0] = k_EPS5ReportIdBluetoothEffects; data[0] = k_EPS5ReportIdBluetoothEffects;
data[1] = 0x02; /* Magic value */ data[1] = 0x02; /* Magic value */
@ -998,7 +1042,7 @@ static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size));
if (device->is_bluetooth) { if (ctx->device->is_bluetooth) {
/* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */ /* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */
Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */ Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */
Uint32 unCRC; Uint32 unCRC;
@ -1012,7 +1056,7 @@ static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
} }
/* See if we can update an existing pending request */ /* See if we can update an existing pending request */
if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size)) { if (SDL_HIDAPI_GetPendingRumbleLocked(ctx->device, &pending_data, &pending_size, &maximum_size)) {
DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset]; DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset];
DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset]; DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];
if (report_size == *pending_size && if (report_size == *pending_size &&
@ -1025,23 +1069,31 @@ static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
} }
} }
if (SDL_HIDAPI_SendRumbleAndUnlock(device, data, report_size) != report_size) { if (SDL_HIDAPI_SendRumbleAndUnlock(ctx->device, data, report_size) != report_size) {
return -1; return -1;
} }
return 0; return 0;
} }
static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size)
{
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
return HIDAPI_DriverPS5_InternalSendJoystickEffect(ctx, effect, size, SDL_TRUE);
}
static int HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled) static int HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
{ {
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
if (!ctx->enhanced_mode_available) { HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx);
if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) {
return SDL_Unsupported(); return SDL_Unsupported();
} }
if (enabled) { if (enabled) {
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
HIDAPI_DriverPS5_LoadCalibrationData(device); HIDAPI_DriverPS5_LoadCalibrationData(device);
} }
ctx->report_sensors = enabled; ctx->report_sensors = enabled;
@ -1422,10 +1474,9 @@ static SDL_bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
} }
break; break;
case k_EPS5ReportIdBluetoothState: case k_EPS5ReportIdBluetoothState:
if (!ctx->enhanced_mode) { /* This is the extended report, we can enable effects now in default mode */
/* This is the extended report, we can enable effects now */ HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx);
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
}
HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, device->dev, ctx, (PS5StatePacketCommon_t *)&data[2]); HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, device->dev, ctx, (PS5StatePacketCommon_t *)&data[2]);
if (ctx->use_alternate_report) { if (ctx->use_alternate_report) {
HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2]); HIDAPI_DriverPS5_HandleStatePacketAlt(joystick, device->dev, ctx, (PS5StatePacketAlt_t *)&data[2]);
@ -1433,7 +1484,7 @@ static SDL_bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2]); HIDAPI_DriverPS5_HandleStatePacket(joystick, device->dev, ctx, (PS5StatePacket_t *)&data[2]);
} }
if (ctx->led_reset_state == k_EDS5LEDResetStatePending) { if (ctx->led_reset_state == k_EDS5LEDResetStatePending) {
HIDAPI_DriverPS5_CheckPendingLEDReset(device); HIDAPI_DriverPS5_CheckPendingLEDReset(ctx);
} }
break; break;
default: default: