SDL: Added SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL

From abfd0dc683b86fa141a061e781c9ef95703e2403 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 16 May 2024 11:14:54 -0700
Subject: [PATCH] Added SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL

---
 include/SDL3/SDL_hints.h             | 11 +++++++++
 src/joystick/hidapi/SDL_hidapi_ps4.c | 34 +++++++++++++++++++++++++++-
 2 files changed, 44 insertions(+), 1 deletion(-)

diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index ab6d11d93cc63..0ff2727f3f88f 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -1373,6 +1373,17 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_PS4 "SDL_JOYSTICK_HIDAPI_PS4"
 
+/**
+ * A variable controlling the update rate of the PS4 controller over Bluetooth when using the HIDAPI driver.
+ *
+ * This defaults to 4 ms, to match the behavior over USB, and to be more friendly to other Bluetooth devices and older Bluetooth hardware on the computer. It can be set to "1" (1000Hz), "2" (500Hz) and "4" (250Hz)
+ *
+ * This hint can be set anytime, but only takes effect when extended input reports are enabled.
+ *
+ * \since This hint is available since SDL 3.0.0.
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL "SDL_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL"
+
 /**
  * A variable controlling whether extended input reports should be used for
  * PS4 controllers when using the HIDAPI driver.
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index e38e9260573df..36a97db1c2c04 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -158,6 +158,7 @@ typedef struct
     SDL_bool enhanced_reports;
     SDL_bool enhanced_mode;
     SDL_bool enhanced_mode_available;
+    int report_interval;
     SDL_bool report_sensors;
     SDL_bool report_touchpad;
     SDL_bool report_battery;
@@ -793,6 +794,33 @@ static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, c
     }
 }
 
+static void SDLCALL SDL_PS4ReportIntervalHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    const int DEFAULT_REPORT_INTERVAL = 4;
+    SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
+    int new_report_interval = DEFAULT_REPORT_INTERVAL;
+
+    if (hint) {
+        int report_interval = SDL_atoi(hint);
+        switch (report_interval) {
+        case 1:
+        case 2:
+        case 4:
+            // Valid values
+            new_report_interval = report_interval;
+            break;
+        default:
+            break;
+        }
+    }
+
+    if (new_report_interval != ctx->report_interval) {
+        ctx->report_interval = new_report_interval;
+
+        HIDAPI_DriverPS4_UpdateEffects(ctx, SDL_FALSE);
+    }
+}
+
 static void HIDAPI_DriverPS4_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
@@ -836,6 +864,8 @@ static SDL_bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
 
     SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
                         SDL_PS4RumbleHintChanged, ctx);
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
+                        SDL_PS4ReportIntervalHintChanged, ctx);
     return SDL_TRUE;
 }
 
@@ -916,7 +946,7 @@ static int HIDAPI_DriverPS4_InternalSendJoystickEffect(SDL_DriverPS4_Context *ct
 
     if (ctx->device->is_bluetooth && ctx->official_controller) {
         data[0] = k_EPS4ReportIdBluetoothEffects;
-        data[1] = 0xC0 | 0x04; /* Magic value HID + CRC, also sets interval to 4ms for samples */
+        data[1] = 0xC0 | ctx->report_interval; /* Magic value HID + CRC, also sets update interval */
         data[3] = 0x03;        /* 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval */
 
         report_size = 78;
@@ -1318,6 +1348,8 @@ static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti
 
     SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
                         SDL_PS4RumbleHintChanged, ctx);
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
+                        SDL_PS4ReportIntervalHintChanged, ctx);
 
     ctx->joystick = NULL;
 }