Forráskód Böngészése

Added SDL_GameControllerSendEffect() and SDL_JoystickSendEffect() to allow applications to send custom effects to the PS4 and PS5 controllers

See testgamecontroller.c for an example of a custom PS5 trigger effect
Sam Lantinga 3 éve
szülő
commit
d135c0762f
36 módosított fájl, 533 hozzáadás és 186 törlés
  1. 10 0
      include/SDL_gamecontroller.h
  2. 10 0
      include/SDL_joystick.h
  3. 2 0
      src/dynapi/SDL_dynapi_overrides.h
  4. 2 0
      src/dynapi/SDL_dynapi_procs.h
  5. 6 0
      src/joystick/SDL_gamecontroller.c
  6. 18 0
      src/joystick/SDL_joystick.c
  7. 3 0
      src/joystick/SDL_sysjoystick.h
  8. 14 7
      src/joystick/android/SDL_sysjoystick.c
  9. 7 0
      src/joystick/bsd/SDL_bsdjoystick.c
  10. 14 7
      src/joystick/darwin/SDL_iokitjoystick.c
  11. 7 0
      src/joystick/dummy/SDL_sysjoystick.c
  12. 7 0
      src/joystick/emscripten/SDL_sysjoystick.c
  13. 7 0
      src/joystick/haiku/SDL_haikujoystick.cc
  14. 7 0
      src/joystick/hidapi/SDL_hidapi_gamecube.c
  15. 7 0
      src/joystick/hidapi/SDL_hidapi_luna.c
  16. 61 44
      src/joystick/hidapi/SDL_hidapi_ps4.c
  17. 83 64
      src/joystick/hidapi/SDL_hidapi_ps5.c
  18. 7 0
      src/joystick/hidapi/SDL_hidapi_stadia.c
  19. 7 0
      src/joystick/hidapi/SDL_hidapi_steam.c
  20. 7 0
      src/joystick/hidapi/SDL_hidapi_switch.c
  21. 7 0
      src/joystick/hidapi/SDL_hidapi_xbox360.c
  22. 7 0
      src/joystick/hidapi/SDL_hidapi_xbox360w.c
  23. 7 0
      src/joystick/hidapi/SDL_hidapi_xboxone.c
  24. 27 9
      src/joystick/hidapi/SDL_hidapijoystick.c
  25. 1 0
      src/joystick/hidapi/SDL_hidapijoystick_c.h
  26. 7 0
      src/joystick/iphoneos/SDL_mfijoystick.m
  27. 7 0
      src/joystick/linux/SDL_sysjoystick.c
  28. 6 0
      src/joystick/os2/SDL_os2joystick.c
  29. 7 0
      src/joystick/psp/SDL_sysjoystick.c
  30. 17 10
      src/joystick/virtual/SDL_virtualjoystick.c
  31. 11 4
      src/joystick/vita/SDL_sysjoystick.c
  32. 10 3
      src/joystick/windows/SDL_mmjoystick.c
  33. 7 0
      src/joystick/windows/SDL_rawinputjoystick.c
  34. 14 7
      src/joystick/windows/SDL_windows_gaming_input.c
  35. 14 7
      src/joystick/windows/SDL_windowsjoystick.c
  36. 98 24
      test/testgamecontroller.c

+ 10 - 0
include/SDL_gamecontroller.h

@@ -820,6 +820,16 @@ extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasLED(SDL_GameController *ga
  */
 extern DECLSPEC int SDLCALL SDL_GameControllerSetLED(SDL_GameController *gamecontroller, Uint8 red, Uint8 green, Uint8 blue);
 
+/**
+ * Send a controller specific effect packet
+ *
+ * \param gamecontroller The controller to affect
+ * \param data The data to send to the controller
+ * \param size The size of the data to send to the controller
+ * \returns 0, or -1 if this controller or driver doesn't support effect packets
+ */
+extern DECLSPEC int SDLCALL SDL_GameControllerSendEffect(SDL_GameController *gamecontroller, const void *data, int size);
+
 /**
  * Close a game controller previously opened with SDL_GameControllerOpen().
  *

+ 10 - 0
include/SDL_joystick.h

@@ -782,6 +782,16 @@ extern DECLSPEC SDL_bool SDLCALL SDL_JoystickHasLED(SDL_Joystick *joystick);
  */
 extern DECLSPEC int SDLCALL SDL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
 
+/**
+ * Send a joystick specific effect packet
+ *
+ * \param joystick The joystick to affect
+ * \param data The data to send to the joystick
+ * \param size The size of the data to send to the joystick
+ * \returns 0, or -1 if this joystick or driver doesn't support effect packets
+ */
+extern DECLSPEC int SDLCALL SDL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size);
+
 /**
  * Close a joystick previously opened with SDL_JoystickOpen().
  *

+ 2 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -812,3 +812,5 @@
 #define SDL_TLSCleanup SDL_TLSCleanup_REAL
 #define SDL_SetWindowAlwaysOnTop SDL_SetWindowAlwaysOnTop_REAL
 #define SDL_FlashWindow SDL_FlashWindow_REAL
+#define SDL_GameControllerSendEffect SDL_GameControllerSendEffect_REAL
+#define SDL_JoystickSendEffect SDL_JoystickSendEffect_REAL

+ 2 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -877,3 +877,5 @@ SDL_DYNAPI_PROC(int,SDL_GetAudioDeviceSpec,(int a, int b, SDL_AudioSpec *c),(a,b
 SDL_DYNAPI_PROC(void,SDL_TLSCleanup,(void),(),)
 SDL_DYNAPI_PROC(void,SDL_SetWindowAlwaysOnTop,(SDL_Window *a, SDL_bool b),(a,b),)
 SDL_DYNAPI_PROC(int,SDL_FlashWindow,(SDL_Window *a, Uint32 b),(a, b),return)
+SDL_DYNAPI_PROC(int,SDL_GameControllerSendEffect,(SDL_GameController *a, const void *b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_JoystickSendEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return)

+ 6 - 0
src/joystick/SDL_gamecontroller.c

@@ -2411,6 +2411,12 @@ SDL_GameControllerSetLED(SDL_GameController *gamecontroller, Uint8 red, Uint8 gr
     return SDL_JoystickSetLED(SDL_GameControllerGetJoystick(gamecontroller), red, green, blue);
 }
 
+int
+SDL_GameControllerSendEffect(SDL_GameController *gamecontroller, const void *data, int size)
+{
+    return SDL_JoystickSendEffect(SDL_GameControllerGetJoystick(gamecontroller), data, size);
+}
+
 void
 SDL_GameControllerClose(SDL_GameController *gamecontroller)
 {

+ 18 - 0
src/joystick/SDL_joystick.c

@@ -986,6 +986,24 @@ SDL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return result;
 }
 
+int
+SDL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    int result;
+
+    if (!SDL_PrivateJoystickValid(joystick)) {
+        return -1;
+    }
+
+    SDL_LockJoysticks();
+
+    result = joystick->driver->SendEffect(joystick, data, size);
+
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
 /*
  * Close a joystick previously opened with SDL_JoystickOpen()
  */

+ 3 - 0
src/joystick/SDL_sysjoystick.h

@@ -166,6 +166,9 @@ typedef struct _SDL_JoystickDriver
     SDL_bool (*HasLED)(SDL_Joystick *joystick);
     int (*SetLED)(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
 
+    /* General effects */
+    int (*SendEffect)(SDL_Joystick *joystick, const void *data, int size);
+
     /* Sensor functionality */
     int (*SetSensorsEnabled)(SDL_Joystick *joystick, SDL_bool enabled);
 

+ 14 - 7
src/joystick/android/SDL_sysjoystick.c

@@ -581,7 +581,7 @@ ANDROID_JoystickGetDeviceInstanceID(int device_index)
 }
 
 static int
-ANDROID_JoystickOpen(SDL_Joystick * joystick, int device_index)
+ANDROID_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     SDL_joylist_item *item = JoystickByDevIndex(device_index);
 
@@ -605,25 +605,31 @@ ANDROID_JoystickOpen(SDL_Joystick * joystick, int device_index)
 }
 
 static int
-ANDROID_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+ANDROID_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     return SDL_Unsupported();
 }
 
 static int
-ANDROID_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
+ANDROID_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
     return SDL_Unsupported();
 }
 
 static SDL_bool
-ANDROID_JoystickHasLED(SDL_Joystick * joystick)
+ANDROID_JoystickHasLED(SDL_Joystick *joystick)
 {
     return SDL_FALSE;
 }
 
 static int
-ANDROID_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+ANDROID_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int
+ANDROID_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
     return SDL_Unsupported();
 }
@@ -635,7 +641,7 @@ ANDROID_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 }
 
 static void
-ANDROID_JoystickUpdate(SDL_Joystick * joystick)
+ANDROID_JoystickUpdate(SDL_Joystick *joystick)
 {
     SDL_joylist_item *item = (SDL_joylist_item *) joystick->hwdata;
 
@@ -664,7 +670,7 @@ ANDROID_JoystickUpdate(SDL_Joystick * joystick)
 }
 
 static void
-ANDROID_JoystickClose(SDL_Joystick * joystick)
+ANDROID_JoystickClose(SDL_Joystick *joystick)
 {
     SDL_joylist_item *item = (SDL_joylist_item *) joystick->hwdata;
     if (item) {
@@ -715,6 +721,7 @@ SDL_JoystickDriver SDL_ANDROID_JoystickDriver =
     ANDROID_JoystickRumbleTriggers,
     ANDROID_JoystickHasLED,
     ANDROID_JoystickSetLED,
+    ANDROID_JoystickSendEffect,
     ANDROID_JoystickSetSensorsEnabled,
     ANDROID_JoystickUpdate,
     ANDROID_JoystickClose,

+ 7 - 0
src/joystick/bsd/SDL_bsdjoystick.c

@@ -789,6 +789,12 @@ BSD_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return SDL_Unsupported();
 }
 
+static int
+BSD_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 BSD_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -810,6 +816,7 @@ SDL_JoystickDriver SDL_BSD_JoystickDriver =
     BSD_JoystickRumbleTriggers,
     BSD_JoystickHasLED,
     BSD_JoystickSetLED,
+    BSD_JoystickSendEffect,
     BSD_JoystickSetSensorsEnabled,
     BSD_JoystickUpdate,
     BSD_JoystickClose,

+ 14 - 7
src/joystick/darwin/SDL_iokitjoystick.c

@@ -784,7 +784,7 @@ DARWIN_JoystickGetDeviceInstanceID(int device_index)
 }
 
 static int
-DARWIN_JoystickOpen(SDL_Joystick * joystick, int device_index)
+DARWIN_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     recDevice *device = GetDeviceForIndex(device_index);
 
@@ -894,7 +894,7 @@ DARWIN_JoystickInitRumble(recDevice *device, Sint16 magnitude)
 }
 
 static int
-DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+DARWIN_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     HRESULT result;
     recDevice *device = joystick->hwdata;
@@ -934,19 +934,25 @@ DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint
 }
 
 static int
-DARWIN_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
+DARWIN_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
     return SDL_Unsupported();
 }
 
 static SDL_bool
-DARWIN_JoystickHasLED(SDL_Joystick * joystick)
+DARWIN_JoystickHasLED(SDL_Joystick *joystick)
 {
     return SDL_FALSE;
 }
 
 static int
-DARWIN_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+DARWIN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int
+DARWIN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
     return SDL_Unsupported();
 }
@@ -958,7 +964,7 @@ DARWIN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 }
 
 static void
-DARWIN_JoystickUpdate(SDL_Joystick * joystick)
+DARWIN_JoystickUpdate(SDL_Joystick *joystick)
 {
     recDevice *device = joystick->hwdata;
     recElement *element;
@@ -1063,7 +1069,7 @@ DARWIN_JoystickUpdate(SDL_Joystick * joystick)
 }
 
 static void
-DARWIN_JoystickClose(SDL_Joystick * joystick)
+DARWIN_JoystickClose(SDL_Joystick *joystick)
 {
     recDevice *device = joystick->hwdata;
     if (device) {
@@ -1107,6 +1113,7 @@ SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
     DARWIN_JoystickRumbleTriggers,
     DARWIN_JoystickHasLED,
     DARWIN_JoystickSetLED,
+    DARWIN_JoystickSendEffect,
     DARWIN_JoystickSetSensorsEnabled,
     DARWIN_JoystickUpdate,
     DARWIN_JoystickClose,

+ 7 - 0
src/joystick/dummy/SDL_sysjoystick.c

@@ -107,6 +107,12 @@ DUMMY_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return SDL_Unsupported();
 }
 
+static int
+DUMMY_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 DUMMY_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -149,6 +155,7 @@ SDL_JoystickDriver SDL_DUMMY_JoystickDriver =
     DUMMY_JoystickRumbleTriggers,
     DUMMY_JoystickHasLED,
     DUMMY_JoystickSetLED,
+    DUMMY_JoystickSendEffect,
     DUMMY_JoystickSetSensorsEnabled,
     DUMMY_JoystickUpdate,
     DUMMY_JoystickClose,

+ 7 - 0
src/joystick/emscripten/SDL_sysjoystick.c

@@ -426,6 +426,12 @@ EMSCRIPTEN_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8
     return SDL_Unsupported();
 }
 
+static int
+EMSCRIPTEN_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 EMSCRIPTEN_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -447,6 +453,7 @@ SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver =
     EMSCRIPTEN_JoystickRumbleTriggers,
     EMSCRIPTEN_JoystickHasLED,
     EMSCRIPTEN_JoystickSetLED,
+    EMSCRIPTEN_JoystickSendEffect,
     EMSCRIPTEN_JoystickSetSensorsEnabled,
     EMSCRIPTEN_JoystickUpdate,
     EMSCRIPTEN_JoystickClose,

+ 7 - 0
src/joystick/haiku/SDL_haikujoystick.cc

@@ -281,6 +281,12 @@ extern "C"
         return SDL_Unsupported();
     }
 
+
+    static int HAIKU_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+    {
+        return SDL_Unsupported();
+    }
+
     static int HAIKU_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
     {
         return SDL_Unsupported();
@@ -301,6 +307,7 @@ extern "C"
         HAIKU_JoystickRumbleTriggers,
         HAIKU_JoystickHasLED,
         HAIKU_JoystickSetLED,
+        HAIKU_JoystickSendEffect,
         HAIKU_JoystickSetSensorsEnabled,
         HAIKU_JoystickUpdate,
         HAIKU_JoystickClose,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_gamecube.c

@@ -476,6 +476,12 @@ HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *jo
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverGameCube_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverGameCube_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -528,6 +534,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
     HIDAPI_DriverGameCube_RumbleJoystickTriggers,
     HIDAPI_DriverGameCube_HasJoystickLED,
     HIDAPI_DriverGameCube_SetJoystickLED,
+    HIDAPI_DriverGameCube_SendJoystickEffect,
     HIDAPI_DriverGameCube_SetJoystickSensorsEnabled,
     HIDAPI_DriverGameCube_CloseJoystick,
     HIDAPI_DriverGameCube_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_luna.c

@@ -148,6 +148,12 @@ HIDAPI_DriverLuna_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joysti
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverLuna_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverLuna_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -441,6 +447,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna =
     HIDAPI_DriverLuna_RumbleJoystickTriggers,
     HIDAPI_DriverLuna_HasJoystickLED,
     HIDAPI_DriverLuna_SetJoystickLED,
+    HIDAPI_DriverLuna_SendJoystickEffect,
     HIDAPI_DriverLuna_SetJoystickSensorsEnabled,
     HIDAPI_DriverLuna_CloseJoystick,
     HIDAPI_DriverLuna_FreeDevice,

+ 61 - 44
src/joystick/hidapi/SDL_hidapi_ps4.c

@@ -146,6 +146,8 @@ typedef struct {
 } SDL_DriverPS4_Context;
 
 
+static int HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size);
+
 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)
 {
@@ -385,61 +387,26 @@ 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();
-    }
+    DS4EffectsState_t effects;
 
     if (!ctx->enhanced_mode) {
         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];
+    SDL_zero(effects);
 
-    effects->ucRumbleLeft = ctx->rumble_left;
-    effects->ucRumbleRight = ctx->rumble_right;
+    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;
+        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 = SDL_crc32(0, &ubHdr, 1);
-        unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
-        SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
+        SetLedsForPlayerIndex(&effects, ctx->player_index);
     }
-
-    if (SDL_HIDAPI_SendRumble(device, data, report_size) != report_size) {
-        return SDL_SetError("Couldn't send rumble packet");
-    }
-    return 0;
+    return HIDAPI_DriverPS4_SendJoystickEffect(device, ctx->joystick, &effects, sizeof(effects));
 }
 
 static void
@@ -650,6 +617,55 @@ HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
     return HIDAPI_DriverPS4_UpdateEffects(device);
 }
 
+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;
+    Uint8 data[78];
+    int report_size, offset;
+
+    if (!ctx->effects_supported) {
+        return SDL_Unsupported();
+    }
+
+    if (!ctx->enhanced_mode) {
+        HIDAPI_DriverPS4_SetEnhancedMode(device, joystick);
+    }
+
+    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;
+    }
+
+    SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), size));
+
+    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 = SDL_crc32(0, &ubHdr, 1);
+        unCRC = SDL_crc32(unCRC, data, (size_t)(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 int
 HIDAPI_DriverPS4_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -921,6 +937,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 =
     HIDAPI_DriverPS4_RumbleJoystickTriggers,
     HIDAPI_DriverPS4_HasJoystickLED,
     HIDAPI_DriverPS4_SetJoystickLED,
+    HIDAPI_DriverPS4_SendJoystickEffect,
     HIDAPI_DriverPS4_SetJoystickSensorsEnabled,
     HIDAPI_DriverPS4_CloseJoystick,
     HIDAPI_DriverPS4_FreeDevice,

+ 83 - 64
src/joystick/hidapi/SDL_hidapi_ps5.c

@@ -175,6 +175,8 @@ typedef struct {
 } SDL_DriverPS5_Context;
 
 
+static int HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *effect, int size);
+
 static SDL_bool
 HIDAPI_DriverPS5_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)
 {
@@ -375,32 +377,13 @@ static int
 HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_mask)
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
-    DS5EffectsState_t *effects;
-    Uint8 data[78];
-    int report_size, offset;
-    Uint8 *pending_data;
-    int *pending_size;
-    int maximum_size;
+    DS5EffectsState_t effects;
 
     if (!ctx->enhanced_mode) {
         return SDL_Unsupported();
     }
 
-    SDL_zero(data);
-
-    if (ctx->is_bluetooth) {
-        data[0] = k_EPS5ReportIdBluetoothEffects;
-        data[1] = 0x02;  /* Magic value */
-
-        report_size = 78;
-        offset = 2;
-    } else {
-        data[0] = k_EPS5ReportIdUsbEffects;
-
-        report_size = 48;
-        offset = 1;
-    }
-    effects = (DS5EffectsState_t *)&data[offset];
+    SDL_zero(effects);
 
     /* Make sure the Bluetooth connection sequence has completed before sending LED color change */
     if (ctx->is_bluetooth && 
@@ -412,79 +395,53 @@ HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_mask)
     }
 
     if (ctx->rumble_left || ctx->rumble_right) {
-        effects->ucEnableBits1 |= 0x01; /* Enable rumble emulation */
-        effects->ucEnableBits1 |= 0x02; /* Disable audio haptics */
+        effects.ucEnableBits1 |= 0x01; /* Enable rumble emulation */
+        effects.ucEnableBits1 |= 0x02; /* Disable audio haptics */
 
         /* Shift to reduce effective rumble strength to match Xbox controllers */
-        effects->ucRumbleLeft = ctx->rumble_left >> 1;
-        effects->ucRumbleRight = ctx->rumble_right >> 1;
+        effects.ucRumbleLeft = ctx->rumble_left >> 1;
+        effects.ucRumbleRight = ctx->rumble_right >> 1;
     } else {
         /* Leaving emulated rumble bits off will restore audio haptics */
     }
 
     if ((effect_mask & k_EDS5EffectRumbleStart) != 0) {
-        effects->ucEnableBits1 |= 0x02; /* Disable audio haptics */
+        effects.ucEnableBits1 |= 0x02; /* Disable audio haptics */
     }
     if ((effect_mask & k_EDS5EffectRumble) != 0) {
         /* Already handled above */
     }
     if ((effect_mask & k_EDS5EffectLEDReset) != 0) {
-        effects->ucEnableBits2 |= 0x08; /* Reset LED state */
+        effects.ucEnableBits2 |= 0x08; /* Reset LED state */
     }
     if ((effect_mask & k_EDS5EffectLED) != 0) {
-        effects->ucEnableBits2 |= 0x04; /* Enable LED color */
+        effects.ucEnableBits2 |= 0x04; /* Enable LED color */
 
         /* 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;
+            effects.ucLedRed = ctx->led_red;
+            effects.ucLedGreen = ctx->led_green;
+            effects.ucLedBlue = ctx->led_blue;
         } else {
-            SetLedsForPlayerIndex(effects, ctx->player_index);
+            SetLedsForPlayerIndex(&effects, ctx->player_index);
         }
     }
     if ((effect_mask & k_EDS5EffectPadLights) != 0) {
-        effects->ucEnableBits2 |= 0x10; /* Enable touchpad lights */
+        effects.ucEnableBits2 |= 0x10; /* Enable touchpad lights */
 
         if (ctx->player_lights) {
-            SetLightsForPlayerIndex(effects, ctx->player_index);
+            SetLightsForPlayerIndex(&effects, ctx->player_index);
         } else {
-            effects->ucPadLights = 0x00;
+            effects.ucPadLights = 0x00;
         }
     }
     if ((effect_mask & k_EDS5EffectMicLight) != 0) {
-        effects->ucEnableBits2 |= 0x01; /* Enable microphone light */
+        effects.ucEnableBits2 |= 0x01; /* Enable microphone light */
 
-        effects->ucMicLightMode = 0;    /* Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse */
+        effects.ucMicLightMode = 0;    /* Bitmask, 0x00 = off, 0x01 = solid, 0x02 = pulse */
     }
 
-    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 = SDL_crc32(0, &ubHdr, 1);
-        unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
-        SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
-    }
-
-    if (SDL_HIDAPI_LockRumble() < 0) {
-        return -1;
-    }
-
-    /* See if we can update an existing pending request */
-    if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size)) {
-        DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];
-        if (report_size == *pending_size &&
-            effects->ucEnableBits1 == pending_effects->ucEnableBits1 &&
-            effects->ucEnableBits2 == pending_effects->ucEnableBits2) {
-            /* We're simply updating the data for this request */
-            SDL_memcpy(pending_data, data, report_size);
-            SDL_HIDAPI_UnlockRumble();
-            return 0;
-        }
-    }
-
-    return SDL_HIDAPI_SendRumbleAndUnlock(device, data, report_size);
+    return HIDAPI_DriverPS5_SendJoystickEffect(device, ctx->joystick, &effects, sizeof(effects));
 }
 
 static void
@@ -725,6 +682,67 @@ HIDAPI_DriverPS5_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
     return HIDAPI_DriverPS5_UpdateEffects(device, k_EDS5EffectLED);
 }
 
+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;
+    Uint8 data[78];
+    int report_size, offset;
+    Uint8 *pending_data;
+    int *pending_size;
+    int maximum_size;
+
+    if (!ctx->enhanced_mode) {
+        HIDAPI_DriverPS5_SetEnhancedMode(device, joystick);
+    }
+
+    SDL_zero(data);
+
+    if (ctx->is_bluetooth) {
+        data[0] = k_EPS5ReportIdBluetoothEffects;
+        data[1] = 0x02;  /* Magic value */
+
+        report_size = 78;
+        offset = 2;
+    } else {
+        data[0] = k_EPS5ReportIdUsbEffects;
+
+        report_size = 48;
+        offset = 1;
+    }
+
+    SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), size));
+
+    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 = SDL_crc32(0, &ubHdr, 1);
+        unCRC = SDL_crc32(unCRC, data, (size_t)(report_size - sizeof(unCRC)));
+        SDL_memcpy(&data[report_size - sizeof(unCRC)], &unCRC, sizeof(unCRC));
+    }
+
+    if (SDL_HIDAPI_LockRumble() < 0) {
+        return -1;
+    }
+
+    /* See if we can update an existing pending request */
+    if (SDL_HIDAPI_GetPendingRumbleLocked(device, &pending_data, &pending_size, &maximum_size)) {
+        DS5EffectsState_t *effects = (DS5EffectsState_t *)&data[offset];
+        DS5EffectsState_t *pending_effects = (DS5EffectsState_t *)&pending_data[offset];
+        if (report_size == *pending_size &&
+            effects->ucEnableBits1 == pending_effects->ucEnableBits1 &&
+            effects->ucEnableBits2 == pending_effects->ucEnableBits2) {
+            /* We're simply updating the data for this request */
+            SDL_memcpy(pending_data, data, report_size);
+            SDL_HIDAPI_UnlockRumble();
+            return 0;
+        }
+    }
+
+    return SDL_HIDAPI_SendRumbleAndUnlock(device, data, report_size);
+}
+
 static int
 HIDAPI_DriverPS5_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1082,6 +1100,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS5 =
     HIDAPI_DriverPS5_RumbleJoystickTriggers,
     HIDAPI_DriverPS5_HasJoystickLED,
     HIDAPI_DriverPS5_SetJoystickLED,
+    HIDAPI_DriverPS5_SendJoystickEffect,
     HIDAPI_DriverPS5_SetJoystickSensorsEnabled,
     HIDAPI_DriverPS5_CloseJoystick,
     HIDAPI_DriverPS5_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_stadia.c

@@ -141,6 +141,12 @@ HIDAPI_DriverStadia_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joys
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverStadia_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverStadia_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -314,6 +320,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverStadia =
     HIDAPI_DriverStadia_RumbleJoystickTriggers,
     HIDAPI_DriverStadia_HasJoystickLED,
     HIDAPI_DriverStadia_SetJoystickLED,
+    HIDAPI_DriverStadia_SendJoystickEffect,
     HIDAPI_DriverStadia_SetJoystickSensorsEnabled,
     HIDAPI_DriverStadia_CloseJoystick,
     HIDAPI_DriverStadia_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_steam.c

@@ -1058,6 +1058,12 @@ HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joyst
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverSteam_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverSteam_SetSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1207,6 +1213,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam =
     HIDAPI_DriverSteam_RumbleJoystickTriggers,
     HIDAPI_DriverSteam_HasJoystickLED,
     HIDAPI_DriverSteam_SetJoystickLED,
+    HIDAPI_DriverSteam_SendJoystickEffect,
     HIDAPI_DriverSteam_SetSensorsEnabled,
     HIDAPI_DriverSteam_CloseJoystick,
     HIDAPI_DriverSteam_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_switch.c

@@ -1052,6 +1052,12 @@ HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joys
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1497,6 +1503,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch =
     HIDAPI_DriverSwitch_RumbleJoystickTriggers,
     HIDAPI_DriverSwitch_HasJoystickLED,
     HIDAPI_DriverSwitch_SetJoystickLED,
+    HIDAPI_DriverSwitch_SendJoystickEffect,
     HIDAPI_DriverSwitch_SetJoystickSensorsEnabled,
     HIDAPI_DriverSwitch_CloseJoystick,
     HIDAPI_DriverSwitch_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_xbox360.c

@@ -216,6 +216,12 @@ HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joy
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverXbox360_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverXbox360_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -342,6 +348,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 =
     HIDAPI_DriverXbox360_RumbleJoystickTriggers,
     HIDAPI_DriverXbox360_HasJoystickLED,
     HIDAPI_DriverXbox360_SetJoystickLED,
+    HIDAPI_DriverXbox360_SendJoystickEffect,
     HIDAPI_DriverXbox360_SetJoystickSensorsEnabled,
     HIDAPI_DriverXbox360_CloseJoystick,
     HIDAPI_DriverXbox360_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_xbox360w.c

@@ -186,6 +186,12 @@ HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *jo
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverXbox360W_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -339,6 +345,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W =
     HIDAPI_DriverXbox360W_RumbleJoystickTriggers,
     HIDAPI_DriverXbox360W_HasJoystickLED,
     HIDAPI_DriverXbox360W_SetJoystickLED,
+    HIDAPI_DriverXbox360W_SendJoystickEffect,
     HIDAPI_DriverXbox360W_SetJoystickSensorsEnabled,
     HIDAPI_DriverXbox360W_CloseJoystick,
     HIDAPI_DriverXbox360W_FreeDevice,

+ 7 - 0
src/joystick/hidapi/SDL_hidapi_xboxone.c

@@ -439,6 +439,12 @@ HIDAPI_DriverXboxOne_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joy
     return SDL_Unsupported();
 }
 
+static int
+HIDAPI_DriverXboxOne_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1078,6 +1084,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne =
     HIDAPI_DriverXboxOne_RumbleJoystickTriggers,
     HIDAPI_DriverXboxOne_HasJoystickLED,
     HIDAPI_DriverXboxOne_SetJoystickLED,
+    HIDAPI_DriverXboxOne_SendJoystickEffect,
     HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled,
     HIDAPI_DriverXboxOne_CloseJoystick,
     HIDAPI_DriverXboxOne_FreeDevice,

+ 27 - 9
src/joystick/hidapi/SDL_hidapijoystick.c

@@ -565,7 +565,7 @@ HIDAPI_RemapVal(float val, float val_min, float val_max, float output_min, float
 }
 
 static void HIDAPI_JoystickDetect(void);
-static void HIDAPI_JoystickClose(SDL_Joystick * joystick);
+static void HIDAPI_JoystickClose(SDL_Joystick *joystick);
 
 static SDL_bool
 HIDAPI_IsDeviceSupported(Uint16 vendor_id, Uint16 product_id, Uint16 version, const char *name)
@@ -1269,7 +1269,7 @@ HIDAPI_JoystickGetDeviceInstanceID(int device_index)
 }
 
 static int
-HIDAPI_JoystickOpen(SDL_Joystick * joystick, int device_index)
+HIDAPI_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     SDL_JoystickID joystickID;
     SDL_HIDAPI_Device *device = HIDAPI_GetDeviceByIndex(device_index, &joystickID);
@@ -1295,7 +1295,7 @@ HIDAPI_JoystickOpen(SDL_Joystick * joystick, int device_index)
 }
 
 static int
-HIDAPI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+HIDAPI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     int result;
 
@@ -1312,7 +1312,7 @@ HIDAPI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint
 }
 
 static int
-HIDAPI_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
+HIDAPI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
     int result;
 
@@ -1329,7 +1329,7 @@ HIDAPI_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint1
 }
 
 static SDL_bool
-HIDAPI_JoystickHasLED(SDL_Joystick * joystick)
+HIDAPI_JoystickHasLED(SDL_Joystick *joystick)
 {
     SDL_bool result = SDL_FALSE;
 
@@ -1343,7 +1343,7 @@ HIDAPI_JoystickHasLED(SDL_Joystick * joystick)
 }
 
 static int
-HIDAPI_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+HIDAPI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
     int result;
 
@@ -1360,7 +1360,24 @@ HIDAPI_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blu
 }
 
 static int
-HIDAPI_JoystickSetSensorsEnabled(SDL_Joystick * joystick, SDL_bool enabled)
+HIDAPI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    int result;
+
+    if (joystick->hwdata) {
+        SDL_HIDAPI_Device *device = joystick->hwdata->device;
+
+        result = device->driver->SendJoystickEffect(device, joystick, data, size);
+    } else {
+        SDL_SetError("SendEffect failed, device disconnected");
+        result = -1;
+    }
+
+    return result;
+}
+
+static int
+HIDAPI_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
     int result;
 
@@ -1377,13 +1394,13 @@ HIDAPI_JoystickSetSensorsEnabled(SDL_Joystick * joystick, SDL_bool enabled)
 }
 
 static void
-HIDAPI_JoystickUpdate(SDL_Joystick * joystick)
+HIDAPI_JoystickUpdate(SDL_Joystick *joystick)
 {
     /* This is handled in SDL_HIDAPI_UpdateDevices() */
 }
 
 static void
-HIDAPI_JoystickClose(SDL_Joystick * joystick)
+HIDAPI_JoystickClose(SDL_Joystick *joystick)
 {
     if (joystick->hwdata) {
         SDL_HIDAPI_Device *device = joystick->hwdata->device;
@@ -1470,6 +1487,7 @@ SDL_JoystickDriver SDL_HIDAPI_JoystickDriver =
     HIDAPI_JoystickRumbleTriggers,
     HIDAPI_JoystickHasLED,
     HIDAPI_JoystickSetLED,
+    HIDAPI_JoystickSendEffect,
     HIDAPI_JoystickSetSensorsEnabled,
     HIDAPI_JoystickUpdate,
     HIDAPI_JoystickClose,

+ 1 - 0
src/joystick/hidapi/SDL_hidapijoystick_c.h

@@ -99,6 +99,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver
     int (*RumbleJoystickTriggers)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble);
     SDL_bool (*HasJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
     int (*SetJoystickLED)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue);
+    int (*SendJoystickEffect)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, const void *data, int size);
     int (*SetJoystickSensorsEnabled)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, SDL_bool enabled);
     void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
     void (*FreeDevice)(SDL_HIDAPI_Device *device);

+ 7 - 0
src/joystick/iphoneos/SDL_mfijoystick.m

@@ -1283,6 +1283,12 @@ IOS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return SDL_Unsupported();
 }
 
+static int
+IOS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 IOS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1434,6 +1440,7 @@ SDL_JoystickDriver SDL_IOS_JoystickDriver =
     IOS_JoystickRumbleTriggers,
     IOS_JoystickHasLED,
     IOS_JoystickSetLED,
+    IOS_JoystickSendEffect,
     IOS_JoystickSetSensorsEnabled,
     IOS_JoystickUpdate,
     IOS_JoystickClose,

+ 7 - 0
src/joystick/linux/SDL_sysjoystick.c

@@ -1089,6 +1089,12 @@ LINUX_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return SDL_Unsupported();
 }
 
+static int
+LINUX_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 LINUX_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1603,6 +1609,7 @@ SDL_JoystickDriver SDL_LINUX_JoystickDriver =
     LINUX_JoystickRumbleTriggers,
     LINUX_JoystickHasLED,
     LINUX_JoystickSetLED,
+    LINUX_JoystickSendEffect,
     LINUX_JoystickSetSensorsEnabled,
     LINUX_JoystickUpdate,
     LINUX_JoystickClose,

+ 6 - 0
src/joystick/os2/SDL_os2joystick.c

@@ -484,6 +484,11 @@ static int OS2_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Ui
 	return SDL_Unsupported();
 }
 
+static int OS2_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int OS2_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
 	return SDL_Unsupported();
@@ -777,6 +782,7 @@ SDL_JoystickDriver SDL_OS2_JoystickDriver =
 	OS2_JoystickRumbleTriggers,
 	OS2_JoystickHasLED,
 	OS2_JoystickSetLED,
+	OS2_JoystickSendEffect,
 	OS2_JoystickSetSensorsEnabled,
 	OS2_JoystickUpdate,
 	OS2_JoystickClose,

+ 7 - 0
src/joystick/psp/SDL_sysjoystick.c

@@ -220,6 +220,12 @@ PSP_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return SDL_Unsupported();
 }
 
+static int
+PSP_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int PSP_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
     return SDL_Unsupported();
@@ -307,6 +313,7 @@ SDL_JoystickDriver SDL_PSP_JoystickDriver =
     PSP_JoystickRumbleTriggers,
     PSP_JoystickHasLED,
     PSP_JoystickSetLED,
+    PSP_JoystickSendEffect,
     PSP_JoystickSetSensorsEnabled,
     PSP_JoystickUpdate,
     PSP_JoystickClose,

+ 17 - 10
src/joystick/virtual/SDL_virtualjoystick.c

@@ -164,7 +164,7 @@ SDL_JoystickDetachVirtualInner(int device_index)
 
 
 int
-SDL_JoystickSetVirtualAxisInner(SDL_Joystick * joystick, int axis, Sint16 value)
+SDL_JoystickSetVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 value)
 {
     joystick_hwdata *hwdata;
 
@@ -189,7 +189,7 @@ SDL_JoystickSetVirtualAxisInner(SDL_Joystick * joystick, int axis, Sint16 value)
 
 
 int
-SDL_JoystickSetVirtualButtonInner(SDL_Joystick * joystick, int button, Uint8 value)
+SDL_JoystickSetVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8 value)
 {
     joystick_hwdata *hwdata;
 
@@ -214,7 +214,7 @@ SDL_JoystickSetVirtualButtonInner(SDL_Joystick * joystick, int button, Uint8 val
 
 
 int
-SDL_JoystickSetVirtualHatInner(SDL_Joystick * joystick, int hat, Uint8 value)
+SDL_JoystickSetVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value)
 {
     joystick_hwdata *hwdata;
 
@@ -313,7 +313,7 @@ VIRTUAL_JoystickGetDeviceInstanceID(int device_index)
 
 
 static int
-VIRTUAL_JoystickOpen(SDL_Joystick * joystick, int device_index)
+VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
     if (!hwdata) {
@@ -333,27 +333,33 @@ VIRTUAL_JoystickOpen(SDL_Joystick * joystick, int device_index)
 
 
 static int
-VIRTUAL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+VIRTUAL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     return SDL_Unsupported();
 }
 
 static int
-VIRTUAL_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
+VIRTUAL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
     return SDL_Unsupported();
 }
 
 
 static SDL_bool
-VIRTUAL_JoystickHasLED(SDL_Joystick * joystick)
+VIRTUAL_JoystickHasLED(SDL_Joystick *joystick)
 {
     return SDL_FALSE;
 }
 
 
 static int
-VIRTUAL_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+VIRTUAL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int
+VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
     return SDL_Unsupported();
 }
@@ -366,7 +372,7 @@ VIRTUAL_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 
 
 static void
-VIRTUAL_JoystickUpdate(SDL_Joystick * joystick)
+VIRTUAL_JoystickUpdate(SDL_Joystick *joystick)
 {
     joystick_hwdata *hwdata;
     int i;
@@ -393,7 +399,7 @@ VIRTUAL_JoystickUpdate(SDL_Joystick * joystick)
 
 
 static void
-VIRTUAL_JoystickClose(SDL_Joystick * joystick)
+VIRTUAL_JoystickClose(SDL_Joystick *joystick)
 {
     joystick_hwdata *hwdata;
 
@@ -438,6 +444,7 @@ SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver =
     VIRTUAL_JoystickRumbleTriggers,
     VIRTUAL_JoystickHasLED,
     VIRTUAL_JoystickSetLED,
+    VIRTUAL_JoystickSendEffect,
     VIRTUAL_JoystickSetSensorsEnabled,
     VIRTUAL_JoystickUpdate,
     VIRTUAL_JoystickClose,

+ 11 - 4
src/joystick/vita/SDL_sysjoystick.c

@@ -347,7 +347,7 @@ SDL_JoystickGUID VITA_JoystickGetDeviceGUID( int device_index )
 }
 
 static int
-VITA_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+VITA_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     int index = (int) SDL_JoystickInstanceID(joystick);
     SceCtrlActuator act;
@@ -360,13 +360,13 @@ VITA_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16
 }
 
 static int
-VITA_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left, Uint16 right)
+VITA_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left, Uint16 right)
 {
     return SDL_Unsupported();
 }
 
 static SDL_bool
-VITA_JoystickHasLED(SDL_Joystick * joystick)
+VITA_JoystickHasLED(SDL_Joystick *joystick)
 {
     // always return true for now
     return SDL_TRUE;
@@ -374,13 +374,19 @@ VITA_JoystickHasLED(SDL_Joystick * joystick)
 
 
 static int
-VITA_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+VITA_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
     int index = (int) SDL_JoystickInstanceID(joystick);
     sceCtrlSetLightBar(ext_port_map[index], red, green, blue);
     return 0;
 }
 
+static int
+VITA_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 VITA_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -405,6 +411,7 @@ SDL_JoystickDriver SDL_VITA_JoystickDriver =
 
     VITA_JoystickHasLED,
     VITA_JoystickSetLED,
+    VITA_JoystickSendEffect,
     VITA_JoystickSetSensorsEnabled,
 
     VITA_JoystickUpdate,

+ 10 - 3
src/joystick/windows/SDL_mmjoystick.c

@@ -244,7 +244,7 @@ static SDL_JoystickID WINMM_JoystickGetDeviceInstanceID(int device_index)
    It returns 0, or -1 if there is an error.
  */
 static int
-WINMM_JoystickOpen(SDL_Joystick * joystick, int device_index)
+WINMM_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     int index, i;
     int caps_flags[MAX_AXES - 2] =
@@ -345,6 +345,12 @@ WINMM_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
     return SDL_Unsupported();
 }
 
+static int
+WINMM_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int WINMM_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
     return SDL_Unsupported();
@@ -356,7 +362,7 @@ static int WINMM_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enab
  * and update joystick device state.
  */
 static void
-WINMM_JoystickUpdate(SDL_Joystick * joystick)
+WINMM_JoystickUpdate(SDL_Joystick *joystick)
 {
     MMRESULT result;
     int i;
@@ -414,7 +420,7 @@ WINMM_JoystickUpdate(SDL_Joystick * joystick)
 
 /* Function to close a joystick after use */
 static void
-WINMM_JoystickClose(SDL_Joystick * joystick)
+WINMM_JoystickClose(SDL_Joystick *joystick)
 {
     SDL_free(joystick->hwdata);
 }
@@ -496,6 +502,7 @@ SDL_JoystickDriver SDL_WINMM_JoystickDriver =
     WINMM_JoystickRumbleTriggers,
     WINMM_JoystickHasLED,
     WINMM_JoystickSetLED,
+    WINMM_JoystickSendEffect,
     WINMM_JoystickSetSensorsEnabled,
     WINMM_JoystickUpdate,
     WINMM_JoystickClose,

+ 7 - 0
src/joystick/windows/SDL_rawinputjoystick.c

@@ -1289,6 +1289,12 @@ RAWINPUT_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 bl
     return SDL_Unsupported();
 }
 
+static int
+RAWINPUT_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
+{
+    return SDL_Unsupported();
+}
+
 static int
 RAWINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
@@ -1924,6 +1930,7 @@ SDL_JoystickDriver SDL_RAWINPUT_JoystickDriver =
     RAWINPUT_JoystickRumbleTriggers,
     RAWINPUT_JoystickHasLED,
     RAWINPUT_JoystickSetLED,
+    RAWINPUT_JoystickSendEffect,
     RAWINPUT_JoystickSetSensorsEnabled,
     RAWINPUT_JoystickUpdate,
     RAWINPUT_JoystickClose,

+ 14 - 7
src/joystick/windows/SDL_windows_gaming_input.c

@@ -486,7 +486,7 @@ WGI_JoystickGetDeviceInstanceID(int device_index)
 }
 
 static int
-WGI_JoystickOpen(SDL_Joystick * joystick, int device_index)
+WGI_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     WindowsGamingInputControllerState *state = &wgi.controllers[device_index];
     struct joystick_hwdata *hwdata;
@@ -558,7 +558,7 @@ WGI_JoystickOpen(SDL_Joystick * joystick, int device_index)
 }
 
 static int
-WGI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+WGI_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     struct joystick_hwdata *hwdata = joystick->hwdata;
 
@@ -579,7 +579,7 @@ WGI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16
 }
 
 static int
-WGI_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
+WGI_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
     struct joystick_hwdata *hwdata = joystick->hwdata;
 
@@ -600,13 +600,19 @@ WGI_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 r
 }
 
 static SDL_bool
-WGI_JoystickHasLED(SDL_Joystick * joystick)
+WGI_JoystickHasLED(SDL_Joystick *joystick)
 {
     return SDL_FALSE;
 }
 
 static int
-WGI_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+WGI_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int
+WGI_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
     return SDL_Unsupported();
 }
@@ -643,7 +649,7 @@ ConvertHatValue(__x_ABI_CWindows_CGaming_CInput_CGameControllerSwitchPosition va
 }
 
 static void
-WGI_JoystickUpdate(SDL_Joystick * joystick)
+WGI_JoystickUpdate(SDL_Joystick *joystick)
 {
     struct joystick_hwdata *hwdata = joystick->hwdata;
     HRESULT hr;
@@ -677,7 +683,7 @@ WGI_JoystickUpdate(SDL_Joystick * joystick)
 }
 
 static void
-WGI_JoystickClose(SDL_Joystick * joystick)
+WGI_JoystickClose(SDL_Joystick *joystick)
 {
     struct joystick_hwdata *hwdata = joystick->hwdata;
 
@@ -762,6 +768,7 @@ SDL_JoystickDriver SDL_WGI_JoystickDriver =
     WGI_JoystickRumbleTriggers,
     WGI_JoystickHasLED,
     WGI_JoystickSetLED,
+    WGI_JoystickSendEffect,
     WGI_JoystickSetSensorsEnabled,
     WGI_JoystickUpdate,
     WGI_JoystickClose,

+ 14 - 7
src/joystick/windows/SDL_windowsjoystick.c

@@ -526,7 +526,7 @@ WINDOWS_JoystickGetDeviceInstanceID(int device_index)
    It returns 0, or -1 if there is an error.
  */
 static int
-WINDOWS_JoystickOpen(SDL_Joystick * joystick, int device_index)
+WINDOWS_JoystickOpen(SDL_Joystick *joystick, int device_index)
 {
     JoyStick_DeviceData *device = SYS_Joystick;
     int index;
@@ -552,7 +552,7 @@ WINDOWS_JoystickOpen(SDL_Joystick * joystick, int device_index)
 }
 
 static int
-WINDOWS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+WINDOWS_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
     if (joystick->hwdata->bXInputDevice) {
         return SDL_XINPUT_JoystickRumble(joystick, low_frequency_rumble, high_frequency_rumble);
@@ -562,19 +562,25 @@ WINDOWS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uin
 }
 
 static int
-WINDOWS_JoystickRumbleTriggers(SDL_Joystick * joystick, Uint16 left_rumble, Uint16 right_rumble)
+WINDOWS_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
     return SDL_Unsupported();
 }
 
 static SDL_bool
-WINDOWS_JoystickHasLED(SDL_Joystick * joystick)
+WINDOWS_JoystickHasLED(SDL_Joystick *joystick)
 {
     return SDL_FALSE;
 }
 
 static int
-WINDOWS_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
+WINDOWS_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
+{
+    return SDL_Unsupported();
+}
+
+static int
+WINDOWS_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
     return SDL_Unsupported();
 }
@@ -586,7 +592,7 @@ WINDOWS_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 }
 
 static void
-WINDOWS_JoystickUpdate(SDL_Joystick * joystick)
+WINDOWS_JoystickUpdate(SDL_Joystick *joystick)
 {
     if (!joystick->hwdata) {
         return;
@@ -601,7 +607,7 @@ WINDOWS_JoystickUpdate(SDL_Joystick * joystick)
 
 /* Function to close a joystick after use */
 static void
-WINDOWS_JoystickClose(SDL_Joystick * joystick)
+WINDOWS_JoystickClose(SDL_Joystick *joystick)
 {
     if (joystick->hwdata->bXInputDevice) {
         SDL_XINPUT_JoystickClose(joystick);
@@ -659,6 +665,7 @@ SDL_JoystickDriver SDL_WINDOWS_JoystickDriver =
     WINDOWS_JoystickRumbleTriggers,
     WINDOWS_JoystickHasLED,
     WINDOWS_JoystickSetLED,
+    WINDOWS_JoystickSendEffect,
     WINDOWS_JoystickSetSensorsEnabled,
     WINDOWS_JoystickUpdate,
     WINDOWS_JoystickClose,

+ 98 - 24
test/testgamecontroller.c

@@ -61,15 +61,16 @@ static const struct { int x; int y; double angle; } axis_positions[] = {
     {375, -20,   0.0},  /* TRIGGERRIGHT */
 };
 
-SDL_Window *window = NULL;
-SDL_Renderer *screen = NULL;
-SDL_bool retval = SDL_FALSE;
-SDL_bool done = SDL_FALSE;
-SDL_bool set_LED = SDL_FALSE;
-SDL_Texture *background_front, *background_back, *button, *axis;
-SDL_GameController *gamecontroller;
-SDL_GameController **gamecontrollers;
-int num_controllers = 0;
+static SDL_Window *window = NULL;
+static SDL_Renderer *screen = NULL;
+static SDL_bool retval = SDL_FALSE;
+static SDL_bool done = SDL_FALSE;
+static SDL_bool set_LED = SDL_FALSE;
+static int trigger_effect = 0;
+static SDL_Texture *background_front, *background_back, *button, *axis;
+static SDL_GameController *gamecontroller;
+static SDL_GameController **gamecontrollers;
+static int num_controllers = 0;
 
 static void UpdateWindowTitle()
 {
@@ -146,6 +147,7 @@ static void AddController(int device_index, SDL_bool verbose)
     controllers[num_controllers++] = controller;
     gamecontrollers = controllers;
     gamecontroller = controller;
+    trigger_effect = 0;
 
     if (verbose) {
         const char *name = SDL_GameControllerName(gamecontroller);
@@ -245,6 +247,57 @@ static Uint16 ConvertAxisToRumble(Sint16 axis)
     }
 }
 
+/* PS5 trigger effect documentation:
+   https://controllers.fandom.com/wiki/Sony_DualSense#FFB_Trigger_Modes
+*/
+typedef struct
+{
+    Uint8 ucEnableBits1;                /* 0 */
+    Uint8 ucEnableBits2;                /* 1 */
+    Uint8 ucRumbleRight;                /* 2 */
+    Uint8 ucRumbleLeft;                 /* 3 */
+    Uint8 ucHeadphoneVolume;            /* 4 */
+    Uint8 ucSpeakerVolume;              /* 5 */
+    Uint8 ucMicrophoneVolume;           /* 6 */
+    Uint8 ucAudioEnableBits;            /* 7 */
+    Uint8 ucMicLightMode;               /* 8 */
+    Uint8 ucAudioMuteBits;              /* 9 */
+    Uint8 rgucRightTriggerEffect[11];   /* 10 */
+    Uint8 rgucLeftTriggerEffect[11];    /* 21 */
+    Uint8 rgucUnknown1[6];              /* 32 */
+    Uint8 ucLedFlags;                   /* 38 */
+    Uint8 rgucUnknown2[2];              /* 39 */
+    Uint8 ucLedAnim;                    /* 41 */
+    Uint8 ucLedBrightness;              /* 42 */
+    Uint8 ucPadLights;                  /* 43 */
+    Uint8 ucLedRed;                     /* 44 */
+    Uint8 ucLedGreen;                   /* 45 */
+    Uint8 ucLedBlue;                    /* 46 */
+} DS5EffectsState_t;
+
+static void CyclePS5TriggerEffect()
+{
+    DS5EffectsState_t state;
+
+    Uint8 effects[3][11] =
+    {
+        /* Clear trigger effect */
+        { 0x05, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
+        /* Constant resistance across entire trigger pull */
+        { 0x01, 0, 110, 0, 0, 0, 0, 0, 0, 0, 0 },
+        /* Resistance and vibration when trigger is pulled */
+        { 0x06, 15, 63, 128, 0, 0, 0, 0, 0, 0, 0 },
+    };
+
+    trigger_effect = (trigger_effect + 1) % SDL_arraysize(effects);
+
+    SDL_zero(state);
+    state.ucEnableBits1 |= (0x04 | 0x08); /* Modify right and left trigger effect respectively */
+    SDL_memcpy(state.rgucRightTriggerEffect, effects[trigger_effect], sizeof(effects[trigger_effect]));
+    SDL_memcpy(state.rgucLeftTriggerEffect, effects[trigger_effect], sizeof(effects[trigger_effect]));
+    SDL_GameControllerSendEffect(gamecontroller, &state, sizeof(state));
+}
+
 void
 loop(void *arg)
 {
@@ -279,6 +332,8 @@ loop(void *arg)
                 event.ctouchpad.pressure);
             break;
 
+#define VERBOSE_SENSORS
+#ifdef VERBOSE_SENSORS
         case SDL_CONTROLLERSENSORUPDATE:
             SDL_Log("Controller %d sensor %s: %.2f, %.2f, %.2f\n",
                 event.csensor.which,
@@ -288,13 +343,17 @@ loop(void *arg)
                 event.csensor.data[1],
                 event.csensor.data[2]);
             break;
+#endif /* VERBOSE_SENSORS */
 
+#define VERBOSE_AXES
+#ifdef VERBOSE_AXES
         case SDL_CONTROLLERAXISMOTION:
             if (event.caxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.caxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) {
                 SetController(event.caxis.which);
             }
             SDL_Log("Controller %d axis %s changed to %d\n", event.caxis.which, SDL_GameControllerGetStringForAxis((SDL_GameControllerAxis)event.caxis.axis), event.caxis.value);
             break;
+#endif /* VERBOSE_AXES */
 
         case SDL_CONTROLLERBUTTONDOWN:
         case SDL_CONTROLLERBUTTONUP:
@@ -302,6 +361,13 @@ loop(void *arg)
                 SetController(event.cbutton.which);
             }
             SDL_Log("Controller %d button %s %s\n", event.cbutton.which, SDL_GameControllerGetStringForButton((SDL_GameControllerButton)event.cbutton.button), event.cbutton.state ? "pressed" : "released");
+
+            /* Cycle PS5 trigger effects when the microphone button is pressed */
+            if (event.type == SDL_CONTROLLERBUTTONDOWN &&
+                event.cbutton.button == SDL_CONTROLLER_BUTTON_MISC1 &&
+                SDL_GameControllerGetType(gamecontroller) == SDL_CONTROLLER_TYPE_PS5) {
+                CyclePS5TriggerEffect();
+            }
             break;
 
         case SDL_KEYDOWN:
@@ -408,23 +474,25 @@ loop(void *arg)
             }
         }
 
-        /* Update rumble based on trigger state */
-        {
-            Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
-            Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
-            Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
-            Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
-            SDL_GameControllerRumble(gamecontroller, low_frequency_rumble, high_frequency_rumble, 250);
-        }
+        if (trigger_effect == 0) {
+            /* Update rumble based on trigger state */
+            {
+                Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT);
+                Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERRIGHT);
+                Uint16 low_frequency_rumble = ConvertAxisToRumble(left);
+                Uint16 high_frequency_rumble = ConvertAxisToRumble(right);
+                SDL_GameControllerRumble(gamecontroller, low_frequency_rumble, high_frequency_rumble, 250);
+            }
 
-        /* Update trigger rumble based on thumbstick state */
-        {
-            Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
-            Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY);
-            Uint16 left_rumble = ConvertAxisToRumble(~left);
-            Uint16 right_rumble = ConvertAxisToRumble(~right);
+            /* Update trigger rumble based on thumbstick state */
+            {
+                Sint16 left = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
+                Sint16 right = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_RIGHTY);
+                Uint16 left_rumble = ConvertAxisToRumble(~left);
+                Uint16 right_rumble = ConvertAxisToRumble(~right);
 
-            SDL_GameControllerRumbleTriggers(gamecontroller, left_rumble, right_rumble, 250);
+                SDL_GameControllerRumbleTriggers(gamecontroller, left_rumble, right_rumble, 250);
+            }
         }
     }
 
@@ -577,6 +645,12 @@ main(int argc, char *argv[])
     }
 #endif
 
+    /* Reset trigger state */
+    if (trigger_effect != 0) {
+        trigger_effect = -1;
+        CyclePS5TriggerEffect();
+    }
+
     SDL_DestroyRenderer(screen);
     SDL_DestroyWindow(window);
     SDL_QuitSubSystem(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK | SDL_INIT_GAMECONTROLLER);