From 94f17d6c61ebc7cd2223e0ceab2a3e5298eaba0d Mon Sep 17 00:00:00 2001 From: Sanjay Govind Date: Mon, 9 Mar 2026 07:35:57 +1300 Subject: [PATCH] Add support for whammy and tilt on PS4/5 guitars PS4/5 controllers put device specific data into a specific region in the report, so we have to extract it separately. No known guitars use the right stick on the guitar, so to keep things working similarly to PS3, i have opted to map whammy and tilt the same way as the PS3 rb guitars. --- src/joystick/hidapi/SDL_hidapi_ps4.c | 44 ++++++++++++++++++++++- src/joystick/hidapi/SDL_hidapi_ps5.c | 46 +++++++++++++++++++++++- src/joystick/hidapi/SDL_hidapijoystick.c | 2 ++ src/joystick/usb_ids.h | 1 + 4 files changed, 91 insertions(+), 2 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index 3f57a6d30f..62c9ff6273 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -98,6 +98,7 @@ typedef struct Uint8 rgucTouchpadData1[3]; Uint8 ucTouchpadCounter2; Uint8 rgucTouchpadData2[3]; + Uint8 rgucDeviceSpecific[12]; } PS4StatePacket_t; typedef struct @@ -146,6 +147,9 @@ typedef struct bool vibration_supported; bool touchpad_supported; bool effects_supported; + bool guitar_whammy_supported; + bool guitar_tilt_supported; + bool guitar_effects_selector_supported; HIDAPI_PS4_EnhancedReportHint enhanced_report_hint; bool enhanced_reports; bool enhanced_mode; @@ -347,6 +351,7 @@ static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) if (size == 48 && data[2] == 0x27) { Uint8 capabilities = data[4]; Uint8 device_type = data[5]; + Uint8 device_specific_capabilities = data[24]; Uint16 gyro_numerator = LOAD16(data[10], data[11]); Uint16 gyro_denominator = LOAD16(data[12], data[13]); Uint16 accel_numerator = LOAD16(data[14], data[15]); @@ -374,6 +379,15 @@ static bool HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) break; case 0x01: joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + if (device_specific_capabilities & 0x01) { + ctx->guitar_effects_selector_supported = true; + } + if (device_specific_capabilities & 0x02) { + ctx->guitar_tilt_supported = true; + } + if (device_specific_capabilities & 0x04) { + ctx->guitar_whammy_supported = true; + } break; case 0x02: joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; @@ -716,6 +730,10 @@ static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); } + if (ctx->guitar_tilt_supported) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, (float)(1000 / ctx->report_interval)); + } + if (ctx->official_controller) { ctx->report_battery = true; } @@ -983,7 +1001,7 @@ static bool HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(ctx); - if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { + if ((!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) && !ctx->guitar_tilt_supported) { return SDL_Unsupported(); } @@ -1154,6 +1172,30 @@ static void HIDAPI_DriverPS4_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_d SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, sensor_timestamp, data, 3); } + if (ctx->guitar_whammy_supported) { + axis = ((int)packet->rgucDeviceSpecific[1] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + } + + if (ctx->guitar_effects_selector_supported) { + // Align pickup selector mappings with PS3 instruments + static const Sint16 effects_mappings[] = {24576, 11008, -1792, -13568, -26880}; + if (packet->rgucDeviceSpecific[0] < SDL_arraysize(effects_mappings)) { + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[packet->rgucDeviceSpecific[0]]); + } + } + + if (ctx->guitar_tilt_supported) { + float sensor_data[3]; + sensor_data[0] = ((float)packet->rgucDeviceSpecific[2] / 255) * SDL_STANDARD_GRAVITY; + sensor_data[1] = 0; + sensor_data[2] = 0; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + + // Align tilt mappings with PS3 instruments + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, packet->rgucDeviceSpecific[2] > 0xF0); + } + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); } diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index c7903881a9..43e04eaf95 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -156,6 +156,7 @@ typedef struct Uint8 rgucTouchpadData1[3]; // 32 - X/Y, 12 bits per axis Uint8 ucTouchpadCounter2; // 35 - high bit clear + counter Uint8 rgucTouchpadData2[3]; // 36 - X/Y, 12 bits per axis + Uint8 rgucDeviceSpecific[8]; // 40 // There's more unknown data at the end, and a 32-bit CRC on Bluetooth } PS5StatePacketAlt_t; @@ -232,6 +233,9 @@ typedef struct bool playerled_supported; bool touchpad_supported; bool effects_supported; + bool guitar_whammy_supported; + bool guitar_tilt_supported; + bool guitar_effects_selector_supported; HIDAPI_PS5_EnhancedReportHint enhanced_report_hint; bool enhanced_reports; bool enhanced_mode; @@ -448,6 +452,7 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) if (size == 48 && data[2] == 0x28) { Uint8 capabilities = data[4]; Uint8 capabilities2 = data[20]; + Uint8 device_specific_capabilities = data[24]; Uint8 device_type = data[5]; #ifdef DEBUG_PS5_PROTOCOL @@ -475,6 +480,15 @@ static bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) break; case 0x01: joystick_type = SDL_JOYSTICK_TYPE_GUITAR; + if (device_specific_capabilities & 0x01) { + ctx->guitar_effects_selector_supported = true; + } + if (device_specific_capabilities & 0x02) { + ctx->guitar_tilt_supported = true; + } + if (device_specific_capabilities & 0x04) { + ctx->guitar_whammy_supported = true; + } break; case 0x02: joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT; @@ -836,6 +850,11 @@ static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, update_rate); } + if (ctx->guitar_tilt_supported) { + float update_rate = 250.0f; + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, update_rate); + } + ctx->report_battery = true; HIDAPI_UpdateDeviceProperties(ctx->device); @@ -1127,7 +1146,7 @@ static bool HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(ctx); - if (!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) { + if ((!ctx->sensors_supported || (enabled && !ctx->enhanced_mode)) && !ctx->guitar_tilt_supported) { return SDL_Unsupported(); } @@ -1432,6 +1451,7 @@ static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hi static const float TOUCHPAD_SCALEY = 9.34579439e-4f; // 1.0f / 1070 bool touchpad_down; int touchpad_x, touchpad_y; + Sint16 axis; if (ctx->report_touchpad) { touchpad_down = ((packet->ucTouchpadCounter1 & 0x80) == 0); @@ -1447,6 +1467,30 @@ static void HIDAPI_DriverPS5_HandleStatePacketAlt(SDL_Joystick *joystick, SDL_hi HIDAPI_DriverPS5_HandleStatePacketCommon(joystick, dev, ctx, (PS5StatePacketCommon_t *)packet, timestamp); + if (ctx->guitar_whammy_supported) { + axis = ((int)packet->rgucDeviceSpecific[1] * 257) - 32768; + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + } + + if (ctx->guitar_effects_selector_supported) { + // Align pickup selector mappings with PS3 instruments + static const Sint16 effects_mappings[] = {24576, 11008, -1792, -13568, -26880}; + if (packet->rgucDeviceSpecific[0] < SDL_arraysize(effects_mappings)) { + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[packet->rgucDeviceSpecific[0]]); + } + } + + if (ctx->guitar_tilt_supported) { + float sensor_data[3]; + sensor_data[0] = ((float)packet->rgucDeviceSpecific[2] / 255) * SDL_STANDARD_GRAVITY; + sensor_data[1] = 0; + sensor_data[2] = 0; + SDL_SendJoystickSensor(timestamp, joystick, SDL_SENSOR_ACCEL, timestamp, sensor_data, SDL_arraysize(sensor_data)); + + // Align tilt mappings with PS3 instruments + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, packet->rgucDeviceSpecific[2] > 0xF0); + } + SDL_memcpy(&ctx->last_state, packet, sizeof(ctx->last_state)); } diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 475981b9ba..4e8dda32f5 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -174,6 +174,8 @@ bool HIDAPI_SupportsPlaystationDetection(Uint16 vendor, Uint16 product) } switch (vendor) { + case USB_VENDOR_CRKD: + return true; case USB_VENDOR_DRAGONRISE: return true; case USB_VENDOR_HORI: diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 88a61f1cdb..e2afd509da 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -30,6 +30,7 @@ #define USB_VENDOR_ASTRO 0x9886 #define USB_VENDOR_ASUS 0x0b05 #define USB_VENDOR_BACKBONE 0x358a +#define USB_VENDOR_CRKD 0x3651 #define USB_VENDOR_GAMESIR 0x3537 #define USB_VENDOR_DRAGONRISE 0x0079 #define USB_VENDOR_FLYDIGI_V1 0x04b4