From a30adae5676a20729d26cb9616c461853ea53c4a Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Tue, 22 Dec 2020 20:58:32 -0800 Subject: [PATCH] Make it possible to turn on PS4 rumble effects at runtime using the hint --- src/joystick/hidapi/SDL_hidapi_ps4.c | 110 +++++++++++++++++++++++---- src/joystick/hidapi/SDL_hidapi_ps5.c | 33 ++++---- 2 files changed, 113 insertions(+), 30 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index a20757183..4e3235580 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -30,6 +30,7 @@ #include "SDL_timer.h" #include "SDL_joystick.h" #include "SDL_gamecontroller.h" +#include "../../SDL_hints_c.h" #include "../SDL_sysjoystick.h" #include "SDL_hidapijoystick_c.h" #include "SDL_hidapi_rumble.h" @@ -119,11 +120,14 @@ typedef struct { } IMUCalibrationData; typedef struct { + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; SDL_bool is_dongle; SDL_bool is_bluetooth; SDL_bool official_controller; SDL_bool audio_supported; SDL_bool effects_supported; + SDL_bool enhanced_mode; SDL_bool report_sensors; SDL_bool hardware_calibration; IMUCalibrationData calibration[6]; @@ -387,6 +391,10 @@ HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device) return SDL_Unsupported(); } + if (!ctx->enhanced_mode) { + return SDL_Unsupported(); + } + SDL_zero(data); if (ctx->is_bluetooth) { @@ -432,6 +440,32 @@ HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device) return 0; } +static void +HIDAPI_DriverPS4_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + if (!ctx->enhanced_mode) { + ctx->enhanced_mode = SDL_TRUE; + + SDL_PrivateJoystickAddTouchpad(joystick, 2); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL); + + HIDAPI_DriverPS4_UpdateEffects(device); + } +} + +static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + 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); + } +} + static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) { @@ -451,12 +485,15 @@ static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { SDL_DriverPS4_Context *ctx; + SDL_bool enhanced_mode = SDL_FALSE; ctx = (SDL_DriverPS4_Context *)SDL_calloc(1, sizeof(*ctx)); if (!ctx) { SDL_OutOfMemory(); return SDL_FALSE; } + ctx->device = device; + ctx->joystick = joystick; device->dev = hid_open_path(device->path, 0); if (!device->dev) { @@ -471,6 +508,7 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) if (ctx->is_dongle) { ctx->is_bluetooth = SDL_FALSE; ctx->official_controller = SDL_TRUE; + enhanced_mode = SDL_TRUE; } else if (device->vendor_id == USB_VENDOR_SONY) { Uint8 data[USB_PACKET_LENGTH]; int size; @@ -484,13 +522,30 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) data[6], data[5], data[4], data[3], data[2], data[1]); joystick->serial = SDL_strdup(serial); ctx->is_bluetooth = SDL_FALSE; + enhanced_mode = SDL_TRUE; } else { ctx->is_bluetooth = SDL_TRUE; + + /* Read a report to see if we're in enhanced mode */ + size = hid_read_timeout(device->dev, data, sizeof(data), 16); +#ifdef DEBUG_PS4_PROTOCOL + if (size > 0) { + HIDAPI_DumpPacket("PS4 first packet: size = %d", data, size); + } else { + SDL_Log("PS4 first packet: size = %d\n", size); + } +#endif + if (size > 0 && + data[0] >= k_EPS4ReportIdBluetoothState1 && + data[0] <= k_EPS4ReportIdBluetoothState9) { + enhanced_mode = SDL_TRUE; + } } ctx->official_controller = SDL_TRUE; } else { /* Third party controllers appear to all be wired */ ctx->is_bluetooth = SDL_FALSE; + enhanced_mode = SDL_TRUE; } #ifdef DEBUG_PS4 SDL_Log("PS4 dongle = %s, bluetooth = %s\n", ctx->is_dongle ? "TRUE" : "FALSE", ctx->is_bluetooth ? "TRUE" : "FALSE"); @@ -503,28 +558,42 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) } if (HIDAPI_DriverPS4_CanRumble(device->vendor_id, device->product_id)) { - if (ctx->is_bluetooth) { - ctx->effects_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, SDL_FALSE); - } else { - ctx->effects_supported = SDL_TRUE; + ctx->effects_supported = SDL_TRUE; + } + + if (!joystick->serial && device->serial && SDL_strlen(device->serial) == 12) { + int i, j; + char serial[18]; + + j = -1; + for (i = 0; i < 12; i += 2) { + j += 1; + SDL_memcpy(&serial[j], &device->serial[i], 2); + j += 2; + serial[j] = '-'; } + serial[j] = '\0'; + + joystick->serial = SDL_strdup(serial); } /* Initialize player index (needed for setting LEDs) */ ctx->player_index = SDL_JoystickGetPlayerIndex(joystick); - /* Initialize LED and effect state */ - HIDAPI_DriverPS4_UpdateEffects(device); - - /* Initialize the joystick capabilities */ + /* Initialize the joystick capabilities + * + * We can't dynamically add the touchpad button, so always report it here + */ joystick->nbuttons = 16; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; - joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; - - SDL_PrivateJoystickAddTouchpad(joystick, 2); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL); + joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; + if (enhanced_mode) { + HIDAPI_DriverPS4_SetEnhancedMode(device, joystick); + } else { + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, + SDL_PS4RumbleHintChanged, ctx); + } return SDL_TRUE; } @@ -569,6 +638,10 @@ HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti { SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + if (!ctx->enhanced_mode) { + return SDL_Unsupported(); + } + if (enabled) { HIDAPI_DriverPS4_LoadCalibrationData(device); } @@ -729,7 +802,7 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) { SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; SDL_Joystick *joystick = NULL; - Uint8 data[USB_PACKET_LENGTH]; + Uint8 data[USB_PACKET_LENGTH*2]; int size; if (device->num_joysticks > 0) { @@ -756,6 +829,10 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) case k_EPS4ReportIdBluetoothState7: case k_EPS4ReportIdBluetoothState8: case k_EPS4ReportIdBluetoothState9: + if (!ctx->enhanced_mode) { + /* This is the extended report, we can enable effects now */ + HIDAPI_DriverPS4_SetEnhancedMode(device, joystick); + } /* Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present */ if (data[1] & 0x80) { HIDAPI_DriverPS4_HandleStatePacket(joystick, device->dev, ctx, (PS4StatePacket_t*)&data[3]); @@ -779,6 +856,11 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + + SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, + SDL_PS4RumbleHintChanged, ctx); + hid_close(device->dev); device->dev = NULL; diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index 4aa0f9343..5b7553be7 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -154,7 +154,7 @@ typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; SDL_bool is_bluetooth; - SDL_bool effects_supported; + SDL_bool enhanced_mode; SDL_bool report_sensors; SDL_bool hardware_calibration; IMUCalibrationData calibration[6]; @@ -382,7 +382,7 @@ HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, EDS5Effect effect) int *pending_size; int maximum_size; - if (!ctx->effects_supported) { + if (!ctx->enhanced_mode) { return SDL_Unsupported(); } @@ -508,12 +508,12 @@ HIDAPI_DriverPS5_CheckPendingLEDReset(SDL_HIDAPI_Device *device) } static void -HIDAPI_DriverPS5_SetEffectsSupported(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) +HIDAPI_DriverPS5_SetEnhancedMode(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; - if (!ctx->effects_supported) { - ctx->effects_supported = SDL_TRUE; + if (!ctx->enhanced_mode) { + ctx->enhanced_mode = SDL_TRUE; SDL_PrivateJoystickAddTouchpad(joystick, 2); SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO); @@ -530,7 +530,7 @@ static void SDLCALL SDL_PS5RumbleHintChanged(void *userdata, const char *name, c /* 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_SetEffectsSupported(ctx->device, ctx->joystick); + HIDAPI_DriverPS5_SetEnhancedMode(ctx->device, ctx->joystick); } } @@ -556,7 +556,7 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) SDL_DriverPS5_Context *ctx; Uint8 data[USB_PACKET_LENGTH*2]; int size; - SDL_bool effects_supported = SDL_FALSE; + SDL_bool enhanced_mode = SDL_FALSE; ctx = (SDL_DriverPS5_Context *)SDL_calloc(1, sizeof(*ctx)); if (!ctx) { @@ -587,18 +587,18 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) if (size == 64) { /* Connected over USB */ ctx->is_bluetooth = SDL_FALSE; - effects_supported = SDL_TRUE; + enhanced_mode = SDL_TRUE; } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) { /* Connected over Bluetooth, using enhanced reports */ ctx->is_bluetooth = SDL_TRUE; - effects_supported = SDL_TRUE; + enhanced_mode = SDL_TRUE; } else { /* Connected over Bluetooth, using simple reports (DirectInput enabled) */ ctx->is_bluetooth = SDL_TRUE; - effects_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, SDL_FALSE); + enhanced_mode = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, SDL_FALSE); } - if (effects_supported) { + if (enhanced_mode) { /* Read the serial number (Bluetooth address in reverse byte order) This will also enable enhanced reports over Bluetooth */ @@ -636,9 +636,10 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) */ joystick->nbuttons = 17; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; + joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; - if (effects_supported) { - HIDAPI_DriverPS5_SetEffectsSupported(device, joystick); + if (enhanced_mode) { + HIDAPI_DriverPS5_SetEnhancedMode(device, joystick); } else { SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, SDL_PS5RumbleHintChanged, ctx); @@ -691,7 +692,7 @@ HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joysti { SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; - if (!ctx->effects_supported) { + if (!ctx->enhanced_mode) { return SDL_Unsupported(); } @@ -969,9 +970,9 @@ HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) } break; case k_EPS5ReportIdBluetoothState: - if (!ctx->effects_supported) { + if (!ctx->enhanced_mode) { /* This is the extended report, we can enable effects now */ - HIDAPI_DriverPS5_SetEffectsSupported(device, joystick); + HIDAPI_DriverPS5_SetEnhancedMode(device, joystick); } if (ctx->led_reset_state == k_EDS5LEDResetStatePending) { HIDAPI_DriverPS5_CheckPendingLEDReset(device);