SDL: Added the hint SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED to control whether the player LED is set on Xbox 360 controllers

From 5a3adbfdb22e6f3d9d8260356ad9a7e44f8ae829 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 19 Aug 2022 11:11:25 -0700
Subject: [PATCH] Added the hint SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED
 to control whether the player LED is set on Xbox 360 controllers

---
 include/SDL_hints.h                       |  9 ++++
 src/joystick/hidapi/SDL_hidapi_xbox360.c  | 59 ++++++++++++++++++-----
 src/joystick/hidapi/SDL_hidapi_xbox360w.c | 58 +++++++++++++++++++---
 3 files changed, 108 insertions(+), 18 deletions(-)

diff --git a/include/SDL_hints.h b/include/SDL_hints.h
index c13c206241b..d99fe1373f5 100644
--- a/include/SDL_hints.h
+++ b/include/SDL_hints.h
@@ -862,6 +862,15 @@ extern "C" {
  */
 #define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360   "SDL_JOYSTICK_HIDAPI_XBOX_360"
 
+/**
+ *  \brief  A variable controlling whether the player LEDs should be lit to indicate which player is associated with an Xbox 360 controller.
+ *
+ *  This variable can be set to the following values:
+ *    "0"       - player LEDs are not enabled
+ *    "1"       - player LEDs are enabled (the default)
+ */
+#define SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED "SDL_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED"
+
 /**
  *  \brief  A variable controlling whether the HIDAPI driver for XBox 360 wireless controllers should be used.
  *
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c
index 2ac3b17ba73..a3a968ba9fb 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -26,6 +26,7 @@
 #include "SDL_timer.h"
 #include "SDL_joystick.h"
 #include "SDL_gamecontroller.h"
+#include "../../SDL_hints_c.h"
 #include "../SDL_sysjoystick.h"
 #include "SDL_hidapijoystick_c.h"
 #include "SDL_hidapi_rumble.h"
@@ -38,6 +39,9 @@
 
 
 typedef struct {
+    SDL_HIDAPI_Device *device;
+    int player_index;
+    SDL_bool player_lights;
     Uint8 last_state[USB_PACKET_LENGTH];
 } SDL_DriverXbox360_Context;
 
@@ -110,10 +114,10 @@ HIDAPI_DriverXbox360_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 pr
     return NULL;
 }
 
-static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot)
+static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, SDL_bool on)
 {
     const SDL_bool blink = SDL_FALSE;
-    Uint8 mode = (blink ? 0x02 : 0x06) + slot;
+    Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
     Uint8 led_packet[] = { 0x01, 0x03, 0x00 };
 
     led_packet[2] = mode;
@@ -123,6 +127,27 @@ static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot)
     return SDL_TRUE;
 }
 
+static void UpdateSlotLED(SDL_DriverXbox360_Context *ctx)
+{
+    if (ctx->player_lights && ctx->player_lights >= 0) {
+        SetSlotLED(ctx->device->dev, (ctx->player_index % 4), SDL_TRUE);
+    } else {
+        SetSlotLED(ctx->device->dev, 0, SDL_FALSE);
+    }
+}
+
+static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)userdata;
+    SDL_bool player_lights = SDL_GetStringBoolean(hint, SDL_TRUE);
+
+    if (player_lights != ctx->player_lights) {
+        ctx->player_lights = player_lights;
+
+        UpdateSlotLED(ctx);
+    }
+}
+
 static SDL_bool
 HIDAPI_DriverXbox360_InitDevice(SDL_HIDAPI_Device *device)
 {
@@ -138,25 +163,28 @@ HIDAPI_DriverXbox360_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_Joystic
 static void
 HIDAPI_DriverXbox360_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
 {
-    if (!device->dev) {
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
+
+    if (!ctx) {
         return;
     }
-    if (player_index >= 0) {
-        SetSlotLED(device->dev, (player_index % 4));
-    }
+
+    ctx->player_index = player_index;
+
+    UpdateSlotLED(ctx);
 }
 
 static SDL_bool
 HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
     SDL_DriverXbox360_Context *ctx;
-    int player_index;
 
     ctx = (SDL_DriverXbox360_Context *)SDL_calloc(1, sizeof(*ctx));
     if (!ctx) {
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
+    ctx->device = device;
 
     device->dev = SDL_hid_open_path(device->path, 0);
     if (!device->dev) {
@@ -166,11 +194,13 @@ HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst
     }
     device->context = ctx;
 
-    /* Set the controller LED */
-    player_index = SDL_JoystickGetPlayerIndex(joystick);
-    if (player_index >= 0) {
-        SetSlotLED(device->dev, (player_index % 4));
-    }
+    /* Initialize player index (needed for setting LEDs) */
+    ctx->player_index = SDL_JoystickGetPlayerIndex(joystick);
+    ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, SDL_TRUE);
+    UpdateSlotLED(ctx);
+
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
+                        SDL_PlayerLEDHintChanged, ctx);
 
     /* Initialize the joystick capabilities */
     joystick->nbuttons = 15;
@@ -337,6 +367,11 @@ HIDAPI_DriverXbox360_UpdateDevice(SDL_HIDAPI_Device *device)
 static void
 HIDAPI_DriverXbox360_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
+    SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
+
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
+                        SDL_PlayerLEDHintChanged, ctx);
+
     SDL_LockMutex(device->dev_lock);
     {
         if (device->dev) {
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
index a75d1bd599d..2d0ad026ef1 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
@@ -26,6 +26,7 @@
 #include "SDL_timer.h"
 #include "SDL_joystick.h"
 #include "SDL_gamecontroller.h"
+#include "../../SDL_hints_c.h"
 #include "../SDL_sysjoystick.h"
 #include "SDL_hidapijoystick_c.h"
 #include "SDL_hidapi_rumble.h"
@@ -38,7 +39,11 @@
 
 
 typedef struct {
+    SDL_HIDAPI_Device *device;
     SDL_bool connected;
+    SDL_bool opened;
+    int player_index;
+    SDL_bool player_lights;
     Uint8 last_state[USB_PACKET_LENGTH];
 } SDL_DriverXbox360W_Context;
 
@@ -87,10 +92,10 @@ HIDAPI_DriverXbox360W_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 p
     return "Xbox 360 Wireless Controller";
 }
 
-static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot)
+static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot, SDL_bool on)
 {
     const SDL_bool blink = SDL_FALSE;
-    Uint8 mode = (blink ? 0x02 : 0x06) + slot;
+    Uint8 mode = on ? ((blink ? 0x02 : 0x06) + slot) : 0;
     Uint8 led_packet[] = { 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
 
     led_packet[3] = 0x40 + (mode % 0x0e);
@@ -100,6 +105,27 @@ static SDL_bool SetSlotLED(SDL_hid_device *dev, Uint8 slot)
     return SDL_TRUE;
 }
 
+static void UpdateSlotLED(SDL_DriverXbox360W_Context *ctx)
+{
+    if (ctx->player_lights && ctx->player_lights >= 0) {
+        SetSlotLED(ctx->device->dev, (ctx->player_index % 4), SDL_TRUE);
+    } else {
+        SetSlotLED(ctx->device->dev, 0, SDL_FALSE);
+    }
+}
+
+static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint)
+{
+    SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)userdata;
+    SDL_bool player_lights = SDL_GetStringBoolean(hint, SDL_TRUE);
+
+    if (player_lights != ctx->player_lights) {
+        ctx->player_lights = player_lights;
+
+        UpdateSlotLED(ctx);
+    }
+}
+
 static void
 UpdatePowerLevel(SDL_Joystick *joystick, Uint8 level)
 {
@@ -129,6 +155,7 @@ HIDAPI_DriverXbox360W_InitDevice(SDL_HIDAPI_Device *device)
         SDL_OutOfMemory();
         return SDL_FALSE;
     }
+    ctx->device = device;
 
     device->dev = SDL_hid_open_path(device->path, 0);
     if (!device->dev) {
@@ -155,12 +182,15 @@ HIDAPI_DriverXbox360W_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_Joysti
 static void
 HIDAPI_DriverXbox360W_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index)
 {
-    if (!device->dev) {
+    SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
+
+    if (!ctx) {
         return;
     }
-    if (player_index >= 0) {
-        SetSlotLED(device->dev, (player_index % 4));
-    }
+
+    ctx->player_index = player_index;
+
+    UpdateSlotLED(ctx);
 }
 
 static SDL_bool
@@ -170,6 +200,16 @@ HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joys
 
     SDL_zeroa(ctx->last_state);
 
+    ctx->opened = SDL_TRUE;
+
+    /* Initialize player index (needed for setting LEDs) */
+    ctx->player_index = SDL_JoystickGetPlayerIndex(joystick);
+    ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED, SDL_TRUE);
+    UpdateSlotLED(ctx);
+
+    SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
+                        SDL_PlayerLEDHintChanged, ctx);
+
     /* Initialize the joystick capabilities */
     joystick->nbuttons = 15;
     joystick->naxes = SDL_CONTROLLER_AXIS_MAX;
@@ -339,6 +379,12 @@ HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
 static void
 HIDAPI_DriverXbox360W_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
 {
+    SDL_DriverXbox360W_Context *ctx = (SDL_DriverXbox360W_Context *)device->context;
+
+    SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_XBOX_360_PLAYER_LED,
+                        SDL_PlayerLEDHintChanged, ctx);
+
+    ctx->opened = SDL_FALSE;
 }
 
 static void