SDL: Added SDL_HINT_JOYSTICK_ENHANCED_REPORTS

From 2c0a8363a5073bfb23ddb18d7c75f43e624db570 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 2 Jan 2025 14:27:38 -0800
Subject: [PATCH] Added SDL_HINT_JOYSTICK_ENHANCED_REPORTS

This hint defaults on, enabling advanced controller features.

This replaces SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE and SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, and is supported by PlayStation and Nintendo Switch controllers.

Fixes https://github.com/libsdl-org/SDL/issues/10086
---
 build-scripts/SDL_migration.cocci       |   8 +
 docs/README-migration.md                |   6 +-
 include/SDL3/SDL_hints.h                |  70 +++------
 include/SDL3/SDL_oldnames.h             |   4 +
 src/joystick/hidapi/SDL_hidapi_ps4.c    |  74 +++++----
 src/joystick/hidapi/SDL_hidapi_ps5.c    |  84 +++++------
 src/joystick/hidapi/SDL_hidapi_switch.c | 191 ++++++++++++++++++------
 7 files changed, 252 insertions(+), 185 deletions(-)

diff --git a/build-scripts/SDL_migration.cocci b/build-scripts/SDL_migration.cocci
index 27a549fdd3fe3..dac94493220b5 100644
--- a/build-scripts/SDL_migration.cocci
+++ b/build-scripts/SDL_migration.cocci
@@ -3731,3 +3731,11 @@ typedef SDL_bool, bool;
 @@
 - SDL_GLattr
 + SDL_GLAttr
+@@
+@@
+- SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE
++ SDL_HINT_JOYSTICK_ENHANCED_REPORTS
+@@
+@@
+- SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE
++ SDL_HINT_JOYSTICK_ENHANCED_REPORTS
diff --git a/docs/README-migration.md b/docs/README-migration.md
index c18bf7468942c..ad5886f95d100 100644
--- a/docs/README-migration.md
+++ b/docs/README-migration.md
@@ -805,16 +805,18 @@ The environment variables SDL_VIDEO_X11_WMCLASS and SDL_VIDEO_WAYLAND_WMCLASS ha
 The environment variable AUDIODEV is used exclusively to specify the audio device for the OSS and NetBSD audio drivers. Its use in the ALSA driver has been replaced with the hint SDL_HINT_AUDIO_ALSA_DEFAULT_DEVICE and in the sndio driver with the environment variable AUDIODEVICE.
 
 The following hints have been renamed:
-* SDL_HINT_VIDEODRIVER => SDL_HINT_VIDEO_DRIVER
-* SDL_HINT_AUDIODRIVER => SDL_HINT_AUDIO_DRIVER
 * SDL_HINT_ALLOW_TOPMOST => SDL_HINT_WINDOW_ALLOW_TOPMOST
+* SDL_HINT_AUDIODRIVER => SDL_HINT_AUDIO_DRIVER
 * SDL_HINT_DIRECTINPUT_ENABLED => SDL_HINT_JOYSTICK_DIRECTINPUT
 * SDL_HINT_GDK_TEXTINPUT_DEFAULT => SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT
 * SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE => SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE
+* SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE => SDL_HINT_JOYSTICK_ENHANCED_REPORTS
+* SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE => SDL_HINT_JOYSTICK_ENHANCED_REPORTS
 * SDL_HINT_LINUX_DIGITAL_HATS => SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS
 * SDL_HINT_LINUX_HAT_DEADZONES => SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES
 * SDL_HINT_LINUX_JOYSTICK_CLASSIC => SDL_HINT_JOYSTICK_LINUX_CLASSIC
 * SDL_HINT_LINUX_JOYSTICK_DEADZONES => SDL_HINT_JOYSTICK_LINUX_DEADZONES
+* SDL_HINT_VIDEODRIVER => SDL_HINT_VIDEO_DRIVER
 * SDL_HINT_VIDEO_WAYLAND_EMULATE_MOUSE_WARP => SDL_HINT_MOUSE_EMULATE_WARP_WITH_RELATIVE
 
 The following hints have been removed:
diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h
index 310aa64711f58..62f3c872598d3 100644
--- a/include/SDL3/SDL_hints.h
+++ b/include/SDL3/SDL_hints.h
@@ -1264,6 +1264,25 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_DEVICE "SDL_JOYSTICK_DEVICE"
 
+/**
+ * A variable controlling whether enhanced reports should be used for controllers when using the HIDAPI driver.
+ *
+ * Enhanced reports allow rumble and effects on Bluetooth PlayStation controllers and gyro on Nintendo Switch controllers, but break Windows DirectInput for other applications that don't use SDL.
+ *
+ * Once enhanced reports are enabled, they can't be disabled on PlayStation controllers without power cycling the controller.
+ *
+ * The variable can be set to the following values:
+ *
+ * - "0": enhanced reports are not enabled.
+ * - "1": enhanced reports are enabled. (default)
+ * - "auto": enhanced features are advertised to the application, but SDL doesn't change the controller report mode unless the application uses them.
+ *
+ * This hint can be enabled anytime.
+ *
+ * \since This hint is available since SDL 3.1.3.
+ */
+#define SDL_HINT_JOYSTICK_ENHANCED_REPORTS "SDL_JOYSTICK_ENHANCED_REPORTS"
+
 /**
  * A variable containing a list of flightstick style controllers.
  *
@@ -1571,32 +1590,6 @@ extern "C" {
  */
 #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.
- *
- * The variable can be set to the following values:
- *
- * - "0": extended reports are not enabled. (default)
- * - "1": extended reports are enabled.
- *
- * Extended input reports allow rumble on Bluetooth PS4 controllers, but break
- * DirectInput handling for applications that don't use SDL.
- *
- * Once extended reports are enabled, they can not be disabled without power
- * cycling the controller.
- *
- * For compatibility with applications written for versions of SDL prior to
- * the introduction of PS5 controller support, this value will also control
- * the state of extended reports on PS5 controllers when the
- * SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE hint is not explicitly set.
- *
- * This hint can be enabled anytime.
- *
- * \since This hint is available since SDL 3.1.3.
- */
-#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE"
-
 /**
  * A variable controlling whether the HIDAPI driver for PS5 controllers should
  * be used.
@@ -1627,31 +1620,6 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED"
 
-/**
- * A variable controlling whether extended input reports should be used for
- * PS5 controllers when using the HIDAPI driver.
- *
- * The variable can be set to the following values:
- *
- * - "0": extended reports are not enabled. (default)
- * - "1": extended reports.
- *
- * Extended input reports allow rumble on Bluetooth PS5 controllers, but break
- * DirectInput handling for applications that don't use SDL.
- *
- * Once extended reports are enabled, they can not be disabled without power
- * cycling the controller.
- *
- * For compatibility with applications written for versions of SDL prior to
- * the introduction of PS5 controller support, this value defaults to the
- * value of SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE.
- *
- * This hint can be enabled anytime.
- *
- * \since This hint is available since SDL 3.1.3.
- */
-#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE"
-
 /**
  * A variable controlling whether the HIDAPI driver for NVIDIA SHIELD
  * controllers should be used.
diff --git a/include/SDL3/SDL_oldnames.h b/include/SDL3/SDL_oldnames.h
index db685fa2c650c..c93607e3f41bc 100644
--- a/include/SDL3/SDL_oldnames.h
+++ b/include/SDL3/SDL_oldnames.h
@@ -307,6 +307,8 @@
 #define SDL_HINT_DIRECTINPUT_ENABLED SDL_HINT_JOYSTICK_DIRECTINPUT
 #define SDL_HINT_GDK_TEXTINPUT_DEFAULT SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT
 #define SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE SDL_HINT_JOYSTICK_ENHANCED_REPORTS
+#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE SDL_HINT_JOYSTICK_ENHANCED_REPORTS
 #define SDL_HINT_LINUX_DIGITAL_HATS SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS
 #define SDL_HINT_LINUX_HAT_DEADZONES SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES
 #define SDL_HINT_LINUX_JOYSTICK_CLASSIC SDL_HINT_JOYSTICK_LINUX_CLASSIC
@@ -949,6 +951,8 @@
 #define SDL_HINT_DIRECTINPUT_ENABLED SDL_HINT_DIRECTINPUT_ENABLED_renamed_SDL_HINT_JOYSTICK_DIRECTINPUT
 #define SDL_HINT_GDK_TEXTINPUT_DEFAULT SDL_HINT_GDK_TEXTINPUT_DEFAULT_renamed_SDL_HINT_GDK_TEXTINPUT_DEFAULT_TEXT
 #define SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE_renamed_SDL_HINT_JOYSTICK_HIDAPI_GAMECUBE_RUMBLE_BRAKE
+#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE_renamed_SDL_HINT_JOYSTICK_ENHANCED_REPORTS
+#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE_renamed_SDL_HINT_JOYSTICK_ENHANCED_REPORTS
 #define SDL_HINT_LINUX_DIGITAL_HATS SDL_HINT_LINUX_DIGITAL_HATS_renamed_SDL_HINT_JOYSTICK_LINUX_DIGITAL_HATS
 #define SDL_HINT_LINUX_HAT_DEADZONES SDL_HINT_LINUX_HAT_DEADZONES_renamed_SDL_HINT_JOYSTICK_LINUX_HAT_DEADZONES
 #define SDL_HINT_LINUX_JOYSTICK_CLASSIC SDL_HINT_LINUX_JOYSTICK_CLASSIC_renamed_SDL_HINT_JOYSTICK_LINUX_CLASSIC
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index c10ad84024f87..b1dd6d047a56d 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -129,18 +129,16 @@ typedef struct
 } IMUCalibrationData;
 
 /* Rumble hint mode:
- * default: enhanced features are available if the controller is using enhanced reports
  * "0": enhanced features are never used
  * "1": enhanced features are always used
  * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
  */
 typedef enum
 {
-    PS4_RUMBLE_HINT_DEFAULT,
-    PS4_RUMBLE_HINT_OFF,
-    PS4_RUMBLE_HINT_ON,
-    PS4_RUMBLE_HINT_AUTO
-} SDL_PS4_RumbleHintMode;
+    PS4_ENHANCED_REPORT_HINT_OFF,
+    PS4_ENHANCED_REPORT_HINT_ON,
+    PS4_ENHANCED_REPORT_HINT_AUTO
+} HIDAPI_PS4_EnhancedReportHint;
 
 typedef struct
 {
@@ -154,7 +152,7 @@ typedef struct
     bool vibration_supported;
     bool touchpad_supported;
     bool effects_supported;
-    SDL_PS4_RumbleHintMode rumble_hint;
+    HIDAPI_PS4_EnhancedReportHint enhanced_report_hint;
     bool enhanced_reports;
     bool enhanced_mode;
     bool enhanced_mode_available;
@@ -696,7 +694,7 @@ static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
             SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data));
         }
     } else {
-#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controllers, except it
+#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it
        * only sends reports when the state changes, so we can't disconnect here.
        */
         // We can't even send an invalid effects packet, or it will put the controller in enhanced mode
@@ -709,6 +707,9 @@ static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device)
 
 static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx)
 {
+    if (ctx->enhanced_mode_available) {
+        return;
+    }
     ctx->enhanced_mode_available = true;
 
     if (ctx->touchpad_supported) {
@@ -730,9 +731,7 @@ static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx
 
 static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
 {
-    if (!ctx->enhanced_mode_available) {
-        HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
-    }
+    HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
 
     if (!ctx->enhanced_mode) {
         ctx->enhanced_mode = true;
@@ -742,55 +741,52 @@ static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx)
     }
 }
 
-static void HIDAPI_DriverPS4_SetRumbleHintMode(SDL_DriverPS4_Context *ctx, SDL_PS4_RumbleHintMode rumble_hint)
+static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint)
 {
-    switch (rumble_hint) {
-    case PS4_RUMBLE_HINT_DEFAULT:
-        if (ctx->enhanced_reports) {
-            HIDAPI_DriverPS4_SetEnhancedMode(ctx);
-        }
-        break;
-    case PS4_RUMBLE_HINT_OFF:
+    switch (enhanced_report_hint) {
+    case PS4_ENHANCED_REPORT_HINT_OFF:
         // Nothing to do, enhanced mode is a one-way ticket
         break;
-    case PS4_RUMBLE_HINT_ON:
+    case PS4_ENHANCED_REPORT_HINT_ON:
         HIDAPI_DriverPS4_SetEnhancedMode(ctx);
         break;
-    case PS4_RUMBLE_HINT_AUTO:
+    case PS4_ENHANCED_REPORT_HINT_AUTO:
         HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx);
         break;
     }
-    ctx->rumble_hint = rumble_hint;
+    ctx->enhanced_report_hint = enhanced_report_hint;
 }
 
 static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx)
 {
     ctx->enhanced_reports = true;
 
-    if (ctx->rumble_hint == PS4_RUMBLE_HINT_DEFAULT) {
-        HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON);
+    if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {
+        HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
     }
 }
 
 static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx)
 {
-    if (ctx->rumble_hint == PS4_RUMBLE_HINT_AUTO) {
-        HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON);
+    if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) {
+        HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
     }
 }
 
-static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata;
 
-    if (!hint) {
-        HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_DEFAULT);
-    } else if (SDL_strcasecmp(hint, "auto") == 0) {
-        HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_AUTO);
-    } else if (SDL_GetStringBoolean(hint, false)) {
-        HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON);
+    if (ctx->device->is_bluetooth) {
+        if (hint && SDL_strcasecmp(hint, "auto") == 0) {
+            HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO);
+        } else if (SDL_GetStringBoolean(hint, true)) {
+            HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
+        } else {
+            HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF);
+        }
     } else {
-        HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_OFF);
+        HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON);
     }
 }
 
@@ -868,8 +864,8 @@ static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic
 
     SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
                         SDL_PS4ReportIntervalHintChanged, ctx);
-    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
-                        SDL_PS4RumbleHintChanged, ctx);
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
+                        SDL_PS4EnhancedReportsChanged, ctx);
     return true;
 }
 
@@ -1287,7 +1283,7 @@ static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device)
         case k_EPS4ReportIdBluetoothState7:
         case k_EPS4ReportIdBluetoothState8:
         case k_EPS4ReportIdBluetoothState9:
-            // This is the extended report, we can enable effects now in default mode
+            // This is the extended report, we can enable effects now in auto mode
             HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx);
 
             // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present
@@ -1353,8 +1349,8 @@ static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti
 
     SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL,
                         SDL_PS4ReportIntervalHintChanged, ctx);
-    SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE,
-                        SDL_PS4RumbleHintChanged, ctx);
+    SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
+                        SDL_PS4EnhancedReportsChanged, ctx);
 
     ctx->joystick = NULL;
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index fbb1fd1c18b63..688136878e0b6 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -215,18 +215,16 @@ typedef struct
 } IMUCalibrationData;
 
 /* Rumble hint mode:
- * default: enhanced features are available if the controller is using enhanced reports
  * "0": enhanced features are never used
  * "1": enhanced features are always used
  * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
  */
 typedef enum
 {
-    PS5_RUMBLE_HINT_DEFAULT,
-    PS5_RUMBLE_HINT_OFF,
-    PS5_RUMBLE_HINT_ON,
-    PS5_RUMBLE_HINT_AUTO
-} SDL_PS5_RumbleHintMode;
+    PS5_ENHANCED_REPORT_HINT_OFF,
+    PS5_ENHANCED_REPORT_HINT_ON,
+    PS5_ENHANCED_REPORT_HINT_AUTO
+} HIDAPI_PS5_EnhancedReportHint;
 
 typedef struct
 {
@@ -240,7 +238,7 @@ typedef struct
     bool playerled_supported;
     bool touchpad_supported;
     bool effects_supported;
-    SDL_PS5_RumbleHintMode rumble_hint;
+    HIDAPI_PS5_EnhancedReportHint enhanced_report_hint;
     bool enhanced_reports;
     bool enhanced_mode;
     bool enhanced_mode_available;
@@ -803,6 +801,9 @@ static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device)
 
 static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx)
 {
+    if (ctx->enhanced_mode_available) {
+        return;
+    }
     ctx->enhanced_mode_available = true;
 
     if (ctx->touchpad_supported) {
@@ -828,11 +829,9 @@ static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx
 
 static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)
 {
-    if (!ctx->enhanced_mode_available) {
-        HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
-    }
+    HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
 
-    if (!ctx->enhanced_mode && ctx->enhanced_mode_available) {
+    if (!ctx->enhanced_mode) {
         ctx->enhanced_mode = true;
 
         // Switch into enhanced report mode
@@ -843,63 +842,52 @@ static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx)
     }
 }
 
-static void HIDAPI_DriverPS5_SetRumbleHintMode(SDL_DriverPS5_Context *ctx, SDL_PS5_RumbleHintMode rumble_hint)
+static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint)
 {
-    switch (rumble_hint) {
-    case PS5_RUMBLE_HINT_DEFAULT:
-        if (ctx->enhanced_reports) {
-            HIDAPI_DriverPS5_SetEnhancedMode(ctx);
-        }
-        break;
-    case PS5_RUMBLE_HINT_OFF:
+    switch (enhanced_report_hint) {
+    case PS5_ENHANCED_REPORT_HINT_OFF:
         // Nothing to do, enhanced mode is a one-way ticket
         break;
-    case PS5_RUMBLE_HINT_ON:
+    case PS5_ENHANCED_REPORT_HINT_ON:
         HIDAPI_DriverPS5_SetEnhancedMode(ctx);
         break;
-    case PS5_RUMBLE_HINT_AUTO:
+    case PS5_ENHANCED_REPORT_HINT_AUTO:
         HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx);
         break;
     }
-    ctx->rumble_hint = rumble_hint;
+    ctx->enhanced_report_hint = enhanced_report_hint;
 }
 
 static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx)
 {
     ctx->enhanced_reports = true;
 
-    if (ctx->rumble_hint == PS5_RUMBLE_HINT_DEFAULT) {
-        HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON);
+    if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {
+        HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
     }
 }
 
 static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx)
 {
-    if (ctx->rumble_hint == PS5_RUMBLE_HINT_AUTO) {
-        HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON);
+    if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) {
+        HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
     }
 }
 
-static void SDLCALL SDL_PS5RumbleHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata;
 
-    if (!hint) {
-        /* Games written prior the introduction of PS5 controller support in SDL will not be aware of
-           SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, but they did know SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE.
-           To support apps that only knew about the PS4 hint, we'll use the PS4 hint as the default.
-        */
-        hint = SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE);
-    }
-
-    if (!hint) {
-        HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_DEFAULT);
-    } else if (SDL_strcasecmp(hint, "auto") == 0) {
-        HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_AUTO);
-    } else if (SDL_GetStringBoolean(hint, false)) {
-        HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON);
+    if (ctx->device->is_bluetooth) {
+        if (hint && SDL_strcasecmp(hint, "auto") == 0) {
+            HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO);
+        } else if (SDL_GetStringBoolean(hint, true)) {
+            HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
+        } else {
+            HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF);
+        }
     } else {
-        HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_OFF);
+        HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON);
     }
 }
 
@@ -962,9 +950,9 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic
     joystick->nhats = 1;
     joystick->firmware_version = ctx->firmware_version;
 
-    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
-                        SDL_PS5RumbleHintChanged, ctx);
-    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
+                        SDL_PS5EnhancedReportsChanged, ctx);
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
                         SDL_PS5PlayerLEDHintChanged, ctx);
 
     return true;
@@ -1529,7 +1517,7 @@ static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device)
             }
             break;
         case k_EPS5ReportIdBluetoothState:
-            // This is the extended report, we can enable effects now in default mode
+            // This is the extended report, we can enable effects now in auto mode
             HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx);
 
             if (ctx->use_alternate_report) {
@@ -1592,8 +1580,8 @@ static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti
 {
     SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context;
 
-    SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE,
-                        SDL_PS5RumbleHintChanged, ctx);
+    SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS,
+                        SDL_PS5EnhancedReportsChanged, ctx);
 
     SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED,
                         SDL_PS5PlayerLEDHintChanged, ctx);
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 6c11cdd21cbde..b25ae969d0a1e 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -268,6 +268,18 @@ typedef struct
 } SwitchProprietaryOutputPacket_t;
 #pragma pack()
 
+/* Enhanced report hint mode:
+ * "0": enhanced features are never used
+ * "1": enhanced features are always used
+ * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it.
+ */
+typedef enum
+{
+    SWITCH_ENHANCED_REPORT_HINT_OFF,
+    SWITCH_ENHANCED_REPORT_HINT_ON,
+    SWITCH_ENHANCED_REPORT_HINT_AUTO
+} HIDAPI_Switch_EnhancedReportHint;
+
 typedef struct
 {
     SDL_HIDAPI_Device *device;
@@ -283,6 +295,9 @@ typedef struct
     Uint8 m_nCurrentInputMode;
     Uint8 m_rgucMACAddress[6];
     Uint8 m_nCommandNumber;
+    HIDAPI_Switch_EnhancedReportHint m_eEnhancedReportHint;
+    bool m_bEnhancedMode;
+    bool m_bEnhancedModeAvailable;
     SwitchCommonOutputPacket_t m_RumblePacket;
     Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength];
     bool m_bRumbleActive;
@@ -290,6 +305,7 @@ typedef struct
     bool m_bRumblePending;
     bool m_bRumbleZeroPending;
     Uint32 m_unRumblePending;
+    bool m_bSensorsSupported;
     bool m_bReportSensors;
     bool m_bHasSensorData;
     Uint64 m_ulLastInput;
@@ -777,24 +793,25 @@ static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx)
         }
     }
 
-    /* The official Nintendo Switch Pro Controller supports FullControllerState over Bluetooth
-     * just fine. We really should use that, or else the epowerlevel code in HandleFullControllerState
-     * is completely pointless. We need full state if we want battery level and we only care about
-     * battery level over Bluetooth anyway.
-     */
-    if (ctx->device->vendor_id == USB_VENDOR_NINTENDO) {
-        // However, switching to full controller state breaks DirectInput, so let's not do that
-        #if 0
-        input_mode = k_eSwitchInputReportIDs_FullControllerState;
-        #endif
-
-        /* However, Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode,
+    switch (ctx->m_eEnhancedReportHint) {
+    case SWITCH_ENHANCED_REPORT_HINT_OFF:
+        input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
+        break;
+    case SWITCH_ENHANCED_REPORT_HINT_ON:
+        if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState) {
+            input_mode = k_eSwitchInputReportIDs_FullControllerState;
+        }
+        break;
+    case SWITCH_ENHANCED_REPORT_HINT_AUTO:
+        /* Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode,
          * so let's enable full controller state for them.
          */
-        if (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||
-            ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) {
+        if (ctx->device->vendor_id == USB_VENDOR_NINTENDO &&
+            (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT ||
+             ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT)) {
             input_mode = k_eSwitchInputReportIDs_FullControllerState;
         }
+        break;
     }
     return input_mode;
 }
@@ -813,6 +830,103 @@ static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx)
     return input_mode;
 }
 
+static void UpdateInputMode(SDL_DriverSwitch_Context *ctx)
+{
+    Uint8 input_mode;
+
+    if (ctx->m_bReportSensors) {
+        input_mode = GetSensorInputMode(ctx);
+    } else {
+        input_mode = GetDefaultInputMode(ctx);
+    }
+    SetInputMode(ctx, input_mode);
+}
+
+static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx)
+{
+    if (ctx->m_bEnhancedModeAvailable) {
+        return;
+    }
+    ctx->m_bEnhancedModeAvailable = true;
+
+    if (ctx->m_bSensorsSupported) {
+        // Use the right sensor in the combined Joy-Con pair
+        if (!ctx->device->parent ||
+            ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
+            SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 200.0f);
+            SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 200.0f);
+        }
+        if (ctx->device->parent &&
+            ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
+            SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_L, 200.0f);
+            SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_L, 200.0f);
+        }
+        if (ctx->device->parent &&
+            ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
+            SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_R, 200.0f);
+            SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_R, 200.0f);
+        }
+    }
+}
+
+static void SetEnhancedMode(SDL_DriverSwitch_Context *ctx, bool bEnabled)
+{
+    if (bEnabled) {
+        SetEnhancedModeAvailable(ctx);
+    }
+
+    if (bEnabled != ctx->m_bEnhancedMode) {
+        ctx->m_bEnhancedMode = bEnabled;
+
+        UpdateInputMode(ctx);
+    }
+}
+
+static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint)
+{
+    switch (eEnhancedReportHint) {
+    case SWITCH_ENHANCED_REPORT_HINT_OFF:
+        SetEnhancedMode(ctx, false);
+        break;
+    case SWITCH_ENHANCED_REPORT_HINT_ON:
+        SetEnhancedMode(ctx, true);
+        break;
+    case SWITCH_ENHANCED_REPORT_HINT_AUTO:
+        SetEnhancedModeAvailable(ctx);
+        break;
+    }
+    ctx->m_eEnhancedReportHint = eEnhancedReportHint;
+
+    UpdateInputMode(ctx);
+}
+
+static void UpdateEnhancedModeOnEnhancedReport(SDL_DriverSwitch_Context *ctx)
+{
+    if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) {
+        SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);
+    }
+}
+
+static void UpdateEnhancedModeOnApplicationUsage(SDL_DriverSwitch_Context *ctx)
+{
+    if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) {
+        SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);
+    }
+}
+
+static void SDLCALL SDL_EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata;
+
+    if (hint && SDL_strcasecmp(hint, "auto") == 0) {
+        SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_AUTO);
+    } else if (SDL_GetStringBoolean(hint, true)) {
+        SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON);
+    } else {
+        SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_OFF);
+    }
+}
+
 static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled)
 {
     Uint8 imu_data = enabled ? 1 : 0;
@@ -1432,35 +1546,16 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
             ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 &&
             ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) {
             if (LoadIMUCalibration(ctx)) {
-                // Use the right sensor in the combined Joy-Con pair
-                if (!device->parent ||
-                    ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
-                    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 200.0f);
-                    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 200.0f);
-                }
-                if (device->parent &&
-                    ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) {
-                    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_L, 200.0f);
-                    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 200.0f);
-                }
-                if (device->parent &&
-                    ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) {
-                    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_R, 200.0f);
-                    SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_R, 200.0f);
-                }
+                ctx->m_bSensorsSupported = true;
             }
         }
 
-        if (!SetVibrationEnabled(ctx, 1)) {
-            SDL_SetError("Couldn't enable v

(Patch may be truncated, please check the link at the top of this post.)