From c6c116e84710984eeb0c7863bda3171594e101ae Mon Sep 17 00:00:00 2001 From: Sam Lantinga <slouken@libsdl.org> Date: Mon, 16 Nov 2020 10:51:13 -0800 Subject: [PATCH] Implemented setting the LED color on the PS5 controller --- src/joystick/hidapi/SDL_hidapi_ps4.c | 150 ++++++++--------- src/joystick/hidapi/SDL_hidapi_ps5.c | 182 +++++++++++++++++---- src/joystick/hidapi/SDL_hidapijoystick.c | 21 +++ src/joystick/hidapi/SDL_hidapijoystick_c.h | 2 + 4 files changed, 244 insertions(+), 111 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index f48f54eff..515d88d3f 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -106,10 +106,10 @@ typedef struct { SDL_bool is_dongle; SDL_bool is_bluetooth; SDL_bool audio_supported; - SDL_bool rumble_supported; + SDL_bool effects_supported; int player_index; - Uint16 rumble_left; - Uint16 rumble_right; + Uint8 rumble_left; + Uint8 rumble_right; SDL_bool color_set; Uint8 led_red; Uint8 led_green; @@ -120,27 +120,6 @@ typedef struct { } SDL_DriverPS4_Context; -/* Public domain CRC implementation adapted from: - http://home.thep.lu.se/~bjorn/crc/crc32_simple.c -*/ -static Uint32 crc32_for_byte(Uint32 r) -{ - int i; - for(i = 0; i < 8; ++i) { - r = (r & 1? 0: (Uint32)0xEDB88320L) ^ r >> 1; - } - return r ^ (Uint32)0xFF000000L; -} - -static Uint32 crc32(Uint32 crc, const void *data, int count) -{ - int i; - for(i = 0; i < count; ++i) { - crc = crc32_for_byte((Uint8)crc ^ ((const Uint8*)data)[i]) ^ crc >> 8; - } - return crc; -} - static SDL_bool HIDAPI_DriverPS4_IsSupportedDevice(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol) { @@ -235,7 +214,62 @@ HIDAPI_DriverPS4_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID return -1; } -static int HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); +static int +HIDAPI_DriverPS4_UpdateEffects(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; + DS4EffectsState_t *effects; + Uint8 data[78]; + int report_size, offset; + + if (!ctx->effects_supported) { + return SDL_Unsupported(); + } + + SDL_zero(data); + + if (ctx->is_bluetooth) { + data[0] = k_EPS4ReportIdBluetoothEffects; + 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 */ + + report_size = 78; + offset = 6; + } else { + data[0] = k_EPS4ReportIdUsbEffects; + data[1] = 0x07; /* Magic value */ + + report_size = 32; + offset = 4; + } + effects = (DS4EffectsState_t *)&data[offset]; + + effects->ucRumbleLeft = ctx->rumble_left; + effects->ucRumbleRight = ctx->rumble_right; + + /* Populate the LED state with the appropriate color from our lookup table */ + if (ctx->color_set) { + effects->ucLedRed = ctx->led_red; + effects->ucLedGreen = ctx->led_green; + effects->ucLedBlue = ctx->led_blue; + } else { + SetLedsForPlayerIndex(effects, ctx->player_index); + } + + if (ctx->is_bluetooth) { + /* 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 */ + Uint32 unCRC; + unCRC = crc32(0, &ubHdr, 1); + unCRC = crc32(unCRC, data, (Uint32)(report_size - sizeof(unCRC))); + SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); + } + + if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return 0; +} static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) @@ -249,7 +283,7 @@ HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID ctx->player_index = player_index; /* This will set the new LED state based on the new player index */ - HIDAPI_DriverPS4_RumbleJoystick(device, SDL_JoystickFromInstanceID(instance_id), 0, 0); + HIDAPI_DriverPS4_UpdateEffects(device); } static SDL_bool @@ -293,9 +327,9 @@ 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->rumble_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, SDL_FALSE); + ctx->effects_supported = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, SDL_FALSE); } else { - ctx->rumble_supported = SDL_TRUE; + ctx->effects_supported = SDL_TRUE; } } @@ -303,7 +337,7 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) ctx->player_index = SDL_JoystickGetPlayerIndex(joystick); /* Initialize LED and effect state */ - HIDAPI_DriverPS4_RumbleJoystick(device, joystick, 0, 0); + HIDAPI_DriverPS4_UpdateEffects(device); /* Initialize the joystick capabilities */ joystick->nbuttons = 16; @@ -319,60 +353,11 @@ static int HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context; - DS4EffectsState_t *effects; - Uint8 data[78]; - int report_size, offset; - if (!ctx->rumble_supported) { - return SDL_Unsupported(); - } + ctx->rumble_left = (low_frequency_rumble >> 8); + ctx->rumble_right = (high_frequency_rumble >> 8); - /* In order to send rumble, we have to send a complete effect packet */ - SDL_zero(data); - - if (ctx->is_bluetooth) { - data[0] = k_EPS4ReportIdBluetoothEffects; - 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 */ - - report_size = 78; - offset = 6; - } else { - data[0] = k_EPS4ReportIdUsbEffects; - data[1] = 0x07; /* Magic value */ - - report_size = 32; - offset = 4; - } - effects = (DS4EffectsState_t *)&data[offset]; - - ctx->rumble_left = low_frequency_rumble; - ctx->rumble_right = high_frequency_rumble; - effects->ucRumbleLeft = (low_frequency_rumble >> 8); - effects->ucRumbleRight = (high_frequency_rumble >> 8); - - /* Populate the LED state with the appropriate color from our lookup table */ - if (ctx->color_set) { - effects->ucLedRed = ctx->led_red; - effects->ucLedGreen = ctx->led_green; - effects->ucLedBlue = ctx->led_blue; - } else { - SetLedsForPlayerIndex(effects, ctx->player_index); - } - - if (ctx->is_bluetooth) { - /* 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 */ - Uint32 unCRC; - unCRC = crc32(0, &ubHdr, 1); - unCRC = crc32(unCRC, data, (Uint32)(report_size - sizeof(unCRC))); - SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); - } - - if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { - return SDL_SetError("Couldn't send rumble packet"); - } - return 0; + return HIDAPI_DriverPS4_UpdateEffects(device); } static int @@ -397,8 +382,7 @@ HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystic ctx->led_green = green; ctx->led_blue = blue; - /* FIXME: Is there a better way to send this without sending another rumble packet? */ - return HIDAPI_DriverPS4_RumbleJoystick(device, joystick, ctx->rumble_left, ctx->rumble_right); + return HIDAPI_DriverPS4_UpdateEffects(device); } static void diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index 82bdfdf6b..cd5612f20 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -39,7 +39,8 @@ typedef enum { k_EPS5ReportIdState = 0x01, - k_EPS5ReportIDOutput = 0x02, + k_EPS5ReportIdUsbEffects = 0x02, + k_EPS5ReportIdBluetoothEffects = 0x02, k_EPS5ReportIdBluetoothState = 0x31, } EPS5ReportId; @@ -82,29 +83,41 @@ typedef struct Uint8 rgucUnknown1[8]; /* 40 */ Uint8 rgucTimer2[4]; /* 48 - 32 bit little endian */ Uint8 ucBatteryLevel; /* 52 */ - Uint8 ucConnectState; /* 53 - 0x08 = USB, 0x03 = headphone */ + Uint8 ucConnectState; /* 53 - 0x08 = USB, 0x01 = headphone */ /* There's more unknown data at the end, and a 32-bit CRC on Bluetooth */ } PS5StatePacket_t; - -static void ReadFeatureReport(hid_device *dev, Uint8 report_id) +typedef struct { - Uint8 report[USB_PACKET_LENGTH + 1]; - int size; + Uint8 ucEnableBits; + Uint8 ucRumbleRight; + Uint8 ucRumbleLeft; + Uint8 rgucUnknown1[6]; + Uint8 rgucRightTriggerEffect[11]; + Uint8 rgucLeftTriggerEffect[11]; + Uint8 rgucUnknown2[6]; + Uint8 ucLedFlags; + Uint8 rgucUnknown3[2]; + Uint8 ucLedAnim; + Uint8 ucLedBrightness; + Uint8 ucLedToggles; + Uint8 ucLedRed; + Uint8 ucLedGreen; + Uint8 ucLedBlue; +} DS5EffectsState_t; - SDL_memset(report, 0, sizeof(report)); - report[0] = report_id; - size = hid_get_feature_report(dev, report, sizeof(report)); - if (size > 0) { -#ifdef DEBUG_PS5_PROTOCOL - SDL_Log("Report %d\n", report_id); - HIDAPI_DumpPacket("Report: size = %d", report, size); -#endif - } -} +SDL_COMPILE_TIME_ASSERT(X, sizeof(DS5EffectsState_t) == 46); typedef struct { + SDL_bool is_bluetooth; + int player_index; + Uint16 rumble_left; + Uint16 rumble_right; + SDL_bool color_set; + Uint8 led_red; + Uint8 led_green; + Uint8 led_blue; union { PS5SimpleStatePacket_t simple; @@ -128,6 +141,45 @@ HIDAPI_DriverPS5_GetDeviceName(Uint16 vendor_id, Uint16 product_id) return NULL; } +static SDL_bool ReadFeatureReport(hid_device *dev, Uint8 report_id) +{ + Uint8 report[USB_PACKET_LENGTH + 1]; + + SDL_memset(report, 0, sizeof(report)); + report[0] = report_id; + if (hid_get_feature_report(dev, report, sizeof(report)) < 0) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +static void +SetLedsForPlayerIndex(DS5EffectsState_t *effects, int player_index) +{ + /* This list is the same as what hid-sony.c uses in the Linux kernel. + The first 4 values correspond to what the PS4 assigns. + */ + static const Uint8 colors[7][3] = { + { 0x00, 0x00, 0x40 }, /* Blue */ + { 0x40, 0x00, 0x00 }, /* Red */ + { 0x00, 0x40, 0x00 }, /* Green */ + { 0x20, 0x00, 0x20 }, /* Pink */ + { 0x02, 0x01, 0x00 }, /* Orange */ + { 0x00, 0x01, 0x01 }, /* Teal */ + { 0x01, 0x01, 0x01 } /* White */ + }; + + if (player_index >= 0) { + player_index %= SDL_arraysize(colors); + } else { + player_index = 0; + } + + effects->ucLedRed = colors[player_index][0]; + effects->ucLedGreen = colors[player_index][1]; + effects->ucLedBlue = colors[player_index][2]; +} + static SDL_bool HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) { @@ -140,9 +192,74 @@ HIDAPI_DriverPS5_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID return -1; } +static int +HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device) +{ + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + DS5EffectsState_t *effects; + Uint8 data[78]; + int report_size, offset; + + SDL_zero(data); + + if (ctx->is_bluetooth) { + /* FIXME: This doesn't work yet */ + data[0] = k_EPS5ReportIdBluetoothEffects; + data[1] = 0x07; /* Magic value */ + + report_size = 75; + offset = 2; + } else { + data[0] = k_EPS5ReportIdUsbEffects; + data[1] = 0x07; /* Magic value */ + + report_size = 48; + offset = 2; + } + effects = (DS5EffectsState_t *)&data[offset]; + + effects->ucEnableBits = 0x04; /* Enable LED color */ + + effects->ucRumbleLeft = ctx->rumble_left; + effects->ucRumbleRight = ctx->rumble_right; + + /* Populate the LED state with the appropriate color from our lookup table */ + if (ctx->color_set) { + effects->ucLedRed = ctx->led_red; + effects->ucLedGreen = ctx->led_green; + effects->ucLedBlue = ctx->led_blue; + } else { + SetLedsForPlayerIndex(effects, ctx->player_index); + } + + if (ctx->is_bluetooth) { + /* 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 */ + Uint32 unCRC; + unCRC = crc32(0, &ubHdr, 1); + unCRC = crc32(unCRC, data, (Uint32)(report_size - sizeof(unCRC))); + SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC)); + } + + if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) { + return SDL_SetError("Couldn't send rumble packet"); + } + return 0; +} + static void HIDAPI_DriverPS5_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) { + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + if (!ctx) { + return; + } + + ctx->player_index = player_index; + + /* This will set the new LED state based on the new player index */ + HIDAPI_DriverPS5_UpdateEffects(device); } static SDL_bool @@ -169,6 +286,12 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) */ ReadFeatureReport(device->dev, k_EPS5FeatureReportIdSerialNumber); + /* Initialize player index (needed for setting LEDs) */ + ctx->player_index = SDL_JoystickGetPlayerIndex(joystick); + + /* Initialize LED and effect state */ + HIDAPI_DriverPS5_UpdateEffects(device); + /* Initialize the joystick capabilities */ joystick->nbuttons = 17; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; @@ -182,19 +305,12 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) static int HIDAPI_DriverPS5_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble) { - Uint8 data[5]; + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; - /* This works over USB, not over Bluetooth */ - SDL_zero(data); - data[0] = k_EPS5ReportIDOutput; - data[1] = 0x7; /* Magic value */ - data[3] = (high_frequency_rumble >> 8); - data[4] = (low_frequency_rumble >> 8); + ctx->rumble_left = (low_frequency_rumble >> 8); + ctx->rumble_right = (high_frequency_rumble >> 8); - if (SDL_HIDAPI_SendRumble(device, data, sizeof(data)) != sizeof(data)) { - return SDL_SetError("Couldn't send rumble packet"); - } - return 0; + return HIDAPI_DriverPS5_UpdateEffects(device); } static int @@ -212,7 +328,14 @@ HIDAPI_DriverPS5_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystic static int HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) { - return SDL_Unsupported(); + SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; + + ctx->color_set = SDL_TRUE; + ctx->led_red = red; + ctx->led_green = green; + ctx->led_blue = blue; + + return HIDAPI_DriverPS5_UpdateEffects(device); } static void @@ -431,6 +554,9 @@ HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, hid_device *dev, SDL_ touchpad_y = (packet->rgucTouchpadData2[1] >> 4) | ((int)packet->rgucTouchpadData2[2] << 4); SDL_PrivateJoystickTouchpad(joystick, 0, 1, touchpad_state, touchpad_x * TOUCHPAD_SCALEX, touchpad_y * TOUCHPAD_SCALEY, touchpad_state ? 1.0f : 0.0f); + /* Update whether the controller is in USB or Bluetooth data state */ + ctx->is_bluetooth = (packet->ucConnectState & 0x80) ? SDL_FALSE : SDL_TRUE; + SDL_memcpy(&ctx->last_state.state, packet, sizeof(ctx->last_state.state)); } diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index c732cbaa2..1d819d920 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -390,6 +390,27 @@ HIDAPI_ShutdownDiscovery() #endif } +/* Public domain CRC implementation adapted from: + http://home.thep.lu.se/~bjorn/crc/crc32_simple.c +*/ +static Uint32 crc32_for_byte(Uint32 r) +{ + int i; + for(i = 0; i < 8; ++i) { + r = (r & 1? 0: (Uint32)0xEDB88320L) ^ r >> 1; + } + return r ^ (Uint32)0xFF000000L; +} + +Uint32 crc32(Uint32 crc, const void *data, int count) +{ + int i; + for(i = 0; i < count; ++i) { + crc = crc32_for_byte((Uint8)crc ^ ((const Uint8*)data)[i]) ^ crc >> 8; + } + return crc; +} + void HIDAPI_DumpPacket(const char *prefix, Uint8 *data, int size) { diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 4ea3f6029..498c08f31 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -127,6 +127,8 @@ extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickI extern void HIDAPI_DumpPacket(const char *prefix, Uint8 *data, int size); +Uint32 crc32(Uint32 crc, const void *data, int count); + #endif /* SDL_JOYSTICK_HIDAPI_H */ /* vi: set ts=4 sw=4 expandtab: */