Added an "auto" mode for PS4 and PS5 controller rumble hints

This allows the controllers to report that they have sensors and enhanced capabilities, but not actually switch into enhanced mode (breaking DirectInput) unless the application tries to use them.
main
Sam Lantinga 2023-07-17 17:32:57 -07:00
parent bd4f155bbb
commit 2ebbfe7c5b
2 changed files with 136 additions and 89 deletions

View File

@ -33,10 +33,14 @@
#ifdef SDL_JOYSTICK_HIDAPI_PS4
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_PS4_PROTOCOL*/
#if 0
#define DEBUG_PS4_PROTOCOL
#endif
/* Define this if you want to log calibration data */
/*#define DEBUG_PS4_CALIBRATION*/
#if 0
#define DEBUG_PS4_CALIBRATION
#endif
#define BLUETOOTH_DISCONNECT_TIMEOUT_MS 500
@ -131,8 +135,10 @@ typedef struct
SDL_bool touchpad_supported;
SDL_bool effects_supported;
SDL_bool enhanced_mode;
SDL_bool enhanced_mode_available;
SDL_bool report_sensors;
SDL_bool report_touchpad;
SDL_bool report_battery;
SDL_bool hardware_calibration;
IMUCalibrationData calibration[6];
Uint64 last_packet;
@ -627,10 +633,6 @@ static int HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device)
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
DS4EffectsState_t effects;
if (!ctx->enhanced_mode || !ctx->effects_supported) {
return SDL_Unsupported();
}
SDL_zero(effects);
if (ctx->vibration_supported) {
@ -675,23 +677,42 @@ static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
}
}
static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx)
{
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
if (!ctx->effects_supported) {
/* We shouldn't be sending any packets to the controller */
return;
}
if (!ctx->enhanced_mode) {
ctx->enhanced_mode_available = SDL_TRUE;
if (ctx->touchpad_supported) {
SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);
}
if (ctx->sensors_supported) {
SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f);
}
}
static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
{
if (!ctx->enhanced_mode_available) {
HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
}
if (!ctx->enhanced_mode && ctx->enhanced_mode_available) {
ctx->enhanced_mode = SDL_TRUE;
if (ctx->touchpad_supported) {
SDL_PrivateJoystickAddTouchpad(joystick, 2);
ctx->report_touchpad = SDL_TRUE;
}
if (ctx->sensors_supported) {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
if (ctx->device->is_bluetooth && ctx->official_controller) {
ctx->report_battery = SDL_TRUE;
}
HIDAPI_DriverPS4_UpdateEffects(device);
HIDAPI_DriverPS4_UpdateEffects(ctx->device);
}
}
@ -699,9 +720,12 @@ static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, c
{
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
/* This is a one-way trip, you can't switch the controller back to simple report mode */
if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
HIDAPI_DriverPS4_SetEnhancedMode(ctx->device, ctx->joystick);
if (SDL_strcasecmp(hint, "auto") == 0) {
/* Mark the controller as enhanced mode capable, but wait for API calls to enable it */
HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
} 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_SetEnhancedMode(ctx);
}
}
@ -740,11 +764,9 @@ static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
/* Initialize the joystick capabilities */
joystick->nbuttons = ctx->touchpad_supported ? 16 : 15;
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
if (device->is_bluetooth && ctx->official_controller) {
if (device->is_bluetooth) {
/* We'll update this once we're in enhanced mode */
joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN;
} else if (device->is_bluetooth) {
/* We can't get the power status, assume it's full */
joystick->epowerlevel = SDL_JOYSTICK_POWER_FULL;
} else {
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
}
@ -752,7 +774,7 @@ static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
if (ctx->enhanced_mode) {
/* Force initialization when opening the joystick */
ctx->enhanced_mode = SDL_FALSE;
HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
} else {
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
SDL_PS4RumbleHintChanged, ctx);
@ -784,7 +806,7 @@ static Uint32 HIDAPI_DriverPS4_GetJoystickCapabilities(SDL_HIDAPI_Device *device
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
Uint32 result = 0;
if (ctx->enhanced_mode) {
if (ctx->enhanced_mode_available) {
if (ctx->lightbar_supported) {
result |= SDL_JOYCAP_LED;
}
@ -818,12 +840,12 @@ static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
Uint8 data[78];
int report_size, offset;
if (!ctx->effects_supported) {
if (!ctx->enhanced_mode_available) {
return SDL_Unsupported();
}
if (!ctx->enhanced_mode) {
HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
}
SDL_zeroa(data);
@ -864,11 +886,12 @@ static int HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device,
{
SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
if (!ctx->enhanced_mode) {
if (!ctx->enhanced_mode_available) {
return SDL_Unsupported();
}
if (enabled) {
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
HIDAPI_DriverPS4_LoadCalibrationData(device);
}
ctx->report_sensors = enabled;
@ -980,21 +1003,17 @@ static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d
axis = ((int)packet->ucRightJoystickY * 257) - 32768;
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
if (size > 9 && ctx->device->is_bluetooth && ctx->official_controller) {
if (packet->ucBatteryLevel & 0x10) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED);
if (size > 9 && ctx->report_battery) {
/* Battery level ranges from 0 to 10 */
int level = (packet->ucBatteryLevel & 0xF);
if (level == 0) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY);
} else if (level <= 2) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
} else if (level <= 7) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
} else {
/* Battery level ranges from 0 to 10 */
int level = (packet->ucBatteryLevel & 0xF);
if (level == 0) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_EMPTY);
} else if (level <= 2) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_LOW);
} else if (level <= 7) {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_MEDIUM);
} else {
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
}
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_FULL);
}
}
@ -1149,7 +1168,7 @@ static SDL_bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
case k_EPS4ReportIdBluetoothState9:
if (!ctx->enhanced_mode) {
/* This is the extended report, we can enable effects now */
HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
HIDAPI_DriverPS4_SetEnhancedMode(ctx);
}
/* 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);

View File

@ -30,10 +30,14 @@
#ifdef SDL_JOYSTICK_HIDAPI_PS5
/* Define this if you want to log all packets from the controller */
/*#define DEBUG_PS5_PROTOCOL*/
#if 0
#define DEBUG_PS5_PROTOCOL
#endif
/* Define this if you want to log calibration data */
/*#define DEBUG_PS5_CALIBRATION*/
#if 0
#define DEBUG_PS5_CALIBRATION
#endif
#define GYRO_RES_PER_DEGREE 1024.0f
#define ACCEL_RES_PER_G 8192.0f
@ -219,8 +223,10 @@ typedef struct
SDL_bool touchpad_supported;
SDL_bool effects_supported;
SDL_bool enhanced_mode;
SDL_bool enhanced_mode_available;
SDL_bool report_sensors;
SDL_bool report_touchpad;
SDL_bool report_battery;
SDL_bool hardware_calibration;
IMUCalibrationData calibration[6];
Uint16 firmware_version;
@ -388,13 +394,17 @@ static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device)
ctx->enhanced_mode = SDL_TRUE;
} else {
/* Connected over Bluetooth, using simple reports (DirectInput enabled) */
/* 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.
*/
ctx->enhanced_mode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, SDL_FALSE));
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) {
@ -630,14 +640,8 @@ static int HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
DS5EffectsState_t effects;
if (!ctx->enhanced_mode || !ctx->effects_supported) {
return SDL_Unsupported();
}
SDL_zero(effects);
/* Make sure the Bluetooth connection sequence has completed before sending LED color change */
if (device->is_bluetooth &&
if (device->is_bluetooth && ctx->enhanced_mode &&
(effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) {
if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) {
ctx->led_reset_state = k_EDS5LEDResetStatePending;
@ -645,6 +649,8 @@ static int HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_
}
}
SDL_zero(effects);
if (ctx->vibration_supported) {
if (ctx->rumble_left || ctx->rumble_right) {
if (ctx->firmware_version < 0x0224) {
@ -763,33 +769,52 @@ static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)
}
}
static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx)
{
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
if (!ctx->effects_supported) {
/* We shouldn't be sending any packets to the controller */
return;
}
if (!ctx->enhanced_mode) {
ctx->enhanced_mode_available = SDL_TRUE;
if (ctx->touchpad_supported) {
SDL_PrivateJoystickAddTouchpad(ctx->joystick, 2);
}
if (ctx->sensors_supported) {
if (ctx->device->is_bluetooth) {
/* Bluetooth sensor update rate appears to be 1000 Hz */
SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 1000.0f);
SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 1000.0f);
} else {
SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 250.0f);
}
}
}
static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)
{
if (!ctx->enhanced_mode_available) {
HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
}
if (!ctx->enhanced_mode && ctx->enhanced_mode_available) {
ctx->enhanced_mode = SDL_TRUE;
if (ctx->touchpad_supported) {
SDL_PrivateJoystickAddTouchpad(joystick, 2);
ctx->report_touchpad = SDL_TRUE;
}
if (ctx->sensors_supported) {
if (device->is_bluetooth) {
/* Bluetooth sensor update rate appears to be 1000 Hz */
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 1000.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 1000.0f);
} else {
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 250.0f);
SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 250.0f);
}
if (ctx->device->is_bluetooth) {
ctx->report_battery = SDL_TRUE;
}
/* Switch into enhanced report mode */
HIDAPI_DriverPS5_UpdateEffects(device, 0);
HIDAPI_DriverPS5_UpdateEffects(ctx->device, 0);
/* Update the light effects */
HIDAPI_DriverPS5_UpdateEffects(device, (k_EDS5EffectLED | k_EDS5EffectPadLights));
HIDAPI_DriverPS5_UpdateEffects(ctx->device, (k_EDS5EffectLED | k_EDS5EffectPadLights));
}
}
@ -797,9 +822,12 @@ static void SDLCALL SDL_PS5RumbleHintChanged(void *userdata, const char *name, c
{
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
/* This is a one-way trip, you can't switch the controller back to simple report mode */
if (SDL_GetStringBoolean(hint, SDL_FALSE)) {
HIDAPI_DriverPS5_SetEnhancedMode(ctx->device, ctx->joystick);
if (SDL_strcasecmp(hint, "auto") == 0) {
/* Mark the controller as enhanced mode capable, but wait for API calls to enable it */
HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
} 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_SetEnhancedMode(ctx);
}
}
@ -858,13 +886,18 @@ static SDL_bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
joystick->nbuttons = 15;
}
joystick->naxes = SDL_GAMEPAD_AXIS_MAX;
joystick->epowerlevel = device->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED;
if (device->is_bluetooth) {
/* We'll update this once we're in enhanced mode */
joystick->epowerlevel = SDL_JOYSTICK_POWER_UNKNOWN;
} else {
joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED;
}
joystick->firmware_version = ctx->firmware_version;
if (ctx->enhanced_mode) {
/* Force initialization when opening the joystick */
ctx->enhanced_mode = SDL_FALSE;
HIDAPI_DriverPS5_SetEnhancedMode(device, joystick);
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
} else {
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
SDL_PS5RumbleHintChanged, ctx);
@ -903,7 +936,7 @@ static Uint32 HIDAPI_DriverPS5_GetJoystickCapabilities(SDL_HIDAPI_Device *device
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
Uint32 result = 0;
if (ctx->enhanced_mode) {
if (ctx->enhanced_mode_available) {
if (ctx->lightbar_supported) {
result |= SDL_JOYCAP_LED;
}
@ -940,12 +973,12 @@ static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Jo
int *pending_size;
int maximum_size;
if (!ctx->effects_supported) {
if (!ctx->enhanced_mode_available) {
return SDL_Unsupported();
}
if (!ctx->enhanced_mode) {
HIDAPI_DriverPS5_SetEnhancedMode(device, joystick);
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
}
SDL_zeroa(data);
@ -1003,11 +1036,12 @@ static int HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device,
{
SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
if (!ctx->enhanced_mode) {
if (!ctx->enhanced_mode_available) {
return SDL_Unsupported();
}
if (enabled) {
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
HIDAPI_DriverPS5_LoadCalibrationData(device);
}
ctx->report_sensors = enabled;
@ -1274,13 +1308,7 @@ static void HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d
SDL_SendJoystickTouchpad(timestamp, joystick, 0, 1, touchpad_state, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_state ? 1.0f : 0.0f);
}
/* A check of packet->ucBatteryLevel & 0x10 should work as a check for BT vs USB but doesn't
* seem to always work. Possibly related to being 100% charged?
*/
if (!ctx->device->is_bluetooth) {
/* 0x20 set means fully charged */
SDL_SendJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED);
} else {
if (ctx->report_battery) {
/* Battery level ranges from 0 to 10 */
int level = (packet->ucBatteryLevel & 0xF);
if (level == 0) {
@ -1396,7 +1424,7 @@ static SDL_bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
case k_EPS5ReportIdBluetoothState:
if (!ctx->enhanced_mode) {
/* This is the extended report, we can enable effects now */
HIDAPI_DriverPS5_SetEnhancedMode(device, joystick);
HIDAPI_DriverPS5_SetEnhancedMode(ctx);
}
HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, device->dev, ctx, (PS5StatePacketCommon_t *)&data[2]);
if (ctx->use_alternate_report) {