Pārlūkot izejas kodu

Added SDL_JoystickHasLED

Currently, this is only supported by the PS4 HIDAPI driver.
Sam Lantinga 4 gadi atpakaļ
vecāks
revīzija
e555d45331

+ 9 - 0
include/SDL_gamecontroller.h

@@ -404,6 +404,15 @@ extern DECLSPEC Uint8 SDLCALL SDL_GameControllerGetButton(SDL_GameController *ga
  */
 extern DECLSPEC int SDLCALL SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
 
+/**
+ *  Return whether a controller has an LED
+ *
+ *  \param gamecontroller The controller to query
+ *
+ *  \return SDL_TRUE, or SDL_FALSE if this controller does not have a modifiable LED
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_GameControllerHasLED(SDL_GameController *gamecontroller);
+
 /**
  *  Update a controller's LED color.
  *

+ 9 - 0
include/SDL_joystick.h

@@ -432,6 +432,15 @@ extern DECLSPEC Uint8 SDLCALL SDL_JoystickGetButton(SDL_Joystick * joystick,
  */
 extern DECLSPEC int SDLCALL SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble, Uint32 duration_ms);
 
+/**
+ *  Return whether a joystick has an LED
+ *
+ *  \param joystick The joystick to query
+ *
+ *  \return SDL_TRUE, or SDL_FALSE if this joystick does not have a modifiable LED
+ */
+extern DECLSPEC SDL_bool SDLCALL SDL_JoystickHasLED(SDL_Joystick * joystick);
+
 /**
  *  Update a joystick's LED color.
  *

+ 4 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -768,3 +768,7 @@
 #define SDL_AndroidRequestPermission SDL_AndroidRequestPermission_REAL
 #define SDL_OpenURL SDL_OpenURL_REAL
 #define SDL_HasSurfaceRLE SDL_HasSurfaceRLE_REAL
+#define SDL_GameControllerHasLED SDL_GameControllerHasLED_REAL
+#define SDL_GameControllerSetLED SDL_GameControllerSetLED_REAL
+#define SDL_JoystickHasLED SDL_JoystickHasLED_REAL
+#define SDL_JoystickSetLED SDL_JoystickSetLED_REAL

+ 4 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -829,3 +829,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_AndroidRequestPermission,(const char *a),(a),return
 #endif
 SDL_DYNAPI_PROC(int,SDL_OpenURL,(const char *a),(a),return)
 SDL_DYNAPI_PROC(SDL_bool,SDL_HasSurfaceRLE,(SDL_Surface *a),(a),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_GameControllerHasLED,(SDL_GameController *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_GameControllerSetLED,(SDL_GameController *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return)
+SDL_DYNAPI_PROC(SDL_bool,SDL_JoystickHasLED,(SDL_Joystick *a),(a),return)
+SDL_DYNAPI_PROC(int,SDL_JoystickSetLED,(SDL_Joystick *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return)

+ 6 - 0
src/joystick/SDL_gamecontroller.c

@@ -2107,6 +2107,12 @@ SDL_GameControllerRumble(SDL_GameController *gamecontroller, Uint16 low_frequenc
     return SDL_JoystickRumble(SDL_GameControllerGetJoystick(gamecontroller), low_frequency_rumble, high_frequency_rumble, duration_ms);
 }
 
+SDL_bool
+SDL_GameControllerHasLED(SDL_GameController *gamecontroller)
+{
+    return SDL_JoystickHasLED(SDL_GameControllerGetJoystick(gamecontroller));
+}
+
 int
 SDL_GameControllerSetLED(SDL_GameController *gamecontroller, Uint8 red, Uint8 green, Uint8 blue)
 {

+ 18 - 0
src/joystick/SDL_joystick.c

@@ -901,6 +901,24 @@ SDL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16
     return result;
 }
 
+SDL_bool
+SDL_JoystickHasLED(SDL_Joystick * joystick)
+{
+    SDL_bool result;
+
+    if (!SDL_PrivateJoystickValid(joystick)) {
+        return SDL_FALSE;
+    }
+
+    SDL_LockJoysticks();
+
+    result = joystick->driver->HasLED(joystick);
+
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
 int
 SDL_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {

+ 1 - 0
src/joystick/SDL_sysjoystick.h

@@ -128,6 +128,7 @@ typedef struct _SDL_JoystickDriver
     int (*Rumble)(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble);
 
     /* LED functionality */
+    SDL_bool (*HasLED)(SDL_Joystick * joystick);
     int (*SetLED)(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue);
 
     /* Function to update the state of a joystick - called as a device poll.

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

@@ -633,6 +633,12 @@ ANDROID_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uin
     return SDL_Unsupported();
 }
 
+static SDL_bool
+ANDROID_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 ANDROID_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -717,6 +723,7 @@ SDL_JoystickDriver SDL_ANDROID_JoystickDriver =
     ANDROID_JoystickGetDeviceInstanceID,
     ANDROID_JoystickOpen,
     ANDROID_JoystickRumble,
+    ANDROID_JoystickHasLED,
     ANDROID_JoystickSetLED,
     ANDROID_JoystickUpdate,
     ANDROID_JoystickClose,

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

@@ -768,6 +768,12 @@ BSD_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     return SDL_FALSE;
 }
 
+static SDL_bool
+BSD_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 BSD_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -786,6 +792,7 @@ SDL_JoystickDriver SDL_BSD_JoystickDriver =
     BSD_JoystickGetDeviceInstanceID,
     BSD_JoystickOpen,
     BSD_JoystickRumble,
+    BSD_JoystickHasLED,
     BSD_JoystickSetLED,
     BSD_JoystickUpdate,
     BSD_JoystickClose,

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

@@ -922,6 +922,12 @@ DARWIN_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint
     return 0;
 }
 
+static SDL_bool
+DARWIN_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 DARWIN_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1075,6 +1081,7 @@ SDL_JoystickDriver SDL_DARWIN_JoystickDriver =
     DARWIN_JoystickGetDeviceInstanceID,
     DARWIN_JoystickOpen,
     DARWIN_JoystickRumble,
+    DARWIN_JoystickHasLED,
     DARWIN_JoystickSetLED,
     DARWIN_JoystickUpdate,
     DARWIN_JoystickClose,

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

@@ -89,6 +89,12 @@ DUMMY_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint1
     return SDL_Unsupported();
 }
 
+static SDL_bool
+DUMMY_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 DUMMY_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -128,6 +134,7 @@ SDL_JoystickDriver SDL_DUMMY_JoystickDriver =
     DUMMY_JoystickGetDeviceInstanceID,
     DUMMY_JoystickOpen,
     DUMMY_JoystickRumble,
+    DUMMY_JoystickHasLED,
     DUMMY_JoystickSetLED,
     DUMMY_JoystickUpdate,
     DUMMY_JoystickClose,

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

@@ -409,6 +409,12 @@ EMSCRIPTEN_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out)
     return SDL_FALSE;
 }
 
+static SDL_bool
+EMSCRIPTEN_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 EMSCRIPTEN_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -427,6 +433,7 @@ SDL_JoystickDriver SDL_EMSCRIPTEN_JoystickDriver =
     EMSCRIPTEN_JoystickGetDeviceInstanceID,
     EMSCRIPTEN_JoystickOpen,
     EMSCRIPTEN_JoystickRumble,
+    EMSCRIPTEN_JoystickHasLED,
     EMSCRIPTEN_JoystickSetLED,
     EMSCRIPTEN_JoystickUpdate,
     EMSCRIPTEN_JoystickClose,

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

@@ -265,6 +265,11 @@ extern "C"
         return SDL_FALSE;
     }
 
+    static SDL_bool HAIKU_JoystickHasLED(SDL_Joystick * joystick)
+    {
+        return SDL_FALSE;
+    }
+
     static int HAIKU_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
     {
         return SDL_Unsupported();
@@ -282,6 +287,7 @@ extern "C"
         HAIKU_JoystickGetDeviceInstanceID,
         HAIKU_JoystickOpen,
         HAIKU_JoystickRumble,
+        HAIKU_JoystickHasLED,
         HAIKU_JoystickSetLED,
         HAIKU_JoystickUpdate,
         HAIKU_JoystickClose,

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

@@ -363,6 +363,12 @@ HIDAPI_DriverGameCube_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *jo
     return -1;
 }
 
+static SDL_bool
+HIDAPI_DriverGameCube_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 HIDAPI_DriverGameCube_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -408,6 +414,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube =
     HIDAPI_DriverGameCube_UpdateDevice,
     HIDAPI_DriverGameCube_OpenJoystick,
     HIDAPI_DriverGameCube_RumbleJoystick,
+    HIDAPI_DriverGameCube_HasJoystickLED,
     HIDAPI_DriverGameCube_SetJoystickLED,
     HIDAPI_DriverGameCube_CloseJoystick,
     HIDAPI_DriverGameCube_FreeDevice,

+ 9 - 2
src/joystick/hidapi/SDL_hidapi_ps4.c

@@ -102,7 +102,7 @@ typedef struct {
     int player_index;
     Uint16 rumble_left;
     Uint16 rumble_right;
-    Uint8 color_set;
+    SDL_bool color_set;
     Uint8 led_red;
     Uint8 led_green;
     Uint8 led_blue;
@@ -365,12 +365,18 @@ HIDAPI_DriverPS4_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystic
     return 0;
 }
 
+static SDL_bool
+HIDAPI_DriverPS4_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    return SDL_TRUE;
+}
+
 static int
 HIDAPI_DriverPS4_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
     SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)device->context;
 
-    ctx->color_set = 1;
+    ctx->color_set = SDL_TRUE;
     ctx->led_red = red;
     ctx->led_green = green;
     ctx->led_blue = blue;
@@ -564,6 +570,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverPS4 =
     HIDAPI_DriverPS4_UpdateDevice,
     HIDAPI_DriverPS4_OpenJoystick,
     HIDAPI_DriverPS4_RumbleJoystick,
+    HIDAPI_DriverPS4_HasJoystickLED,
     HIDAPI_DriverPS4_SetJoystickLED,
     HIDAPI_DriverPS4_CloseJoystick,
     HIDAPI_DriverPS4_FreeDevice,

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

@@ -1034,6 +1034,13 @@ HIDAPI_DriverSteam_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joyst
     return SDL_Unsupported();
 }
 
+static SDL_bool
+HIDAPI_DriverSteam_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    /* You should use the full Steam Input API for LED support */
+    return SDL_FALSE;
+}
+
 static int
 HIDAPI_DriverSteam_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1171,6 +1178,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSteam =
     HIDAPI_DriverSteam_UpdateDevice,
     HIDAPI_DriverSteam_OpenJoystick,
     HIDAPI_DriverSteam_RumbleJoystick,
+    HIDAPI_DriverSteam_HasJoystickLED,
     HIDAPI_DriverSteam_SetJoystickLED,
     HIDAPI_DriverSteam_CloseJoystick,
     HIDAPI_DriverSteam_FreeDevice,

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

@@ -920,6 +920,13 @@ HIDAPI_DriverSwitch_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joys
     return HIDAPI_DriverSwitch_ActuallyRumbleJoystick(ctx, low_frequency_rumble, high_frequency_rumble);
 }
 
+static SDL_bool
+HIDAPI_DriverSwitch_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    /* Doesn't have an RGB LED, so don't return true here */
+    return SDL_FALSE;
+}
+
 static int
 HIDAPI_DriverSwitch_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1273,6 +1280,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch =
     HIDAPI_DriverSwitch_UpdateDevice,
     HIDAPI_DriverSwitch_OpenJoystick,
     HIDAPI_DriverSwitch_RumbleJoystick,
+    HIDAPI_DriverSwitch_HasJoystickLED,
     HIDAPI_DriverSwitch_SetJoystickLED,
     HIDAPI_DriverSwitch_CloseJoystick,
     HIDAPI_DriverSwitch_FreeDevice,

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

@@ -796,6 +796,13 @@ HIDAPI_DriverXbox360_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joy
     return 0;
 }
 
+static SDL_bool
+HIDAPI_DriverXbox360_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    /* Doesn't have an RGB LED, so don't return true here */
+    return SDL_FALSE;
+}
+
 static int
 HIDAPI_DriverXbox360_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1308,6 +1315,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360 =
     HIDAPI_DriverXbox360_UpdateDevice,
     HIDAPI_DriverXbox360_OpenJoystick,
     HIDAPI_DriverXbox360_RumbleJoystick,
+    HIDAPI_DriverXbox360_HasJoystickLED,
     HIDAPI_DriverXbox360_SetJoystickLED,
     HIDAPI_DriverXbox360_CloseJoystick,
     HIDAPI_DriverXbox360_FreeDevice,

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

@@ -157,6 +157,13 @@ HIDAPI_DriverXbox360W_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *jo
     return 0;
 }
 
+static SDL_bool
+HIDAPI_DriverXbox360W_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    /* Doesn't have an RGB LED, so don't return true here */
+    return SDL_FALSE;
+}
+
 static int
 HIDAPI_DriverXbox360W_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -300,6 +307,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXbox360W =
     HIDAPI_DriverXbox360W_UpdateDevice,
     HIDAPI_DriverXbox360W_OpenJoystick,
     HIDAPI_DriverXbox360W_RumbleJoystick,
+    HIDAPI_DriverXbox360W_HasJoystickLED,
     HIDAPI_DriverXbox360W_SetJoystickLED,
     HIDAPI_DriverXbox360W_CloseJoystick,
     HIDAPI_DriverXbox360W_FreeDevice,

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

@@ -364,6 +364,13 @@ HIDAPI_DriverXboxOne_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joy
     return 0;
 }
 
+static SDL_bool
+HIDAPI_DriverXboxOne_HasJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick)
+{
+    /* Doesn't have an RGB LED, so don't return true here */
+    return SDL_FALSE;
+}
+
 static int
 HIDAPI_DriverXboxOne_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -836,6 +843,7 @@ SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverXboxOne =
     HIDAPI_DriverXboxOne_UpdateDevice,
     HIDAPI_DriverXboxOne_OpenJoystick,
     HIDAPI_DriverXboxOne_RumbleJoystick,
+    HIDAPI_DriverXboxOne_HasJoystickLED,
     HIDAPI_DriverXboxOne_SetJoystickLED,
     HIDAPI_DriverXboxOne_CloseJoystick,
     HIDAPI_DriverXboxOne_FreeDevice,

+ 15 - 0
src/joystick/hidapi/SDL_hidapijoystick.c

@@ -1048,6 +1048,20 @@ HIDAPI_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint
     return result;
 }
 
+static SDL_bool
+HIDAPI_JoystickHasLED(SDL_Joystick * joystick)
+{
+    SDL_bool result = SDL_FALSE;
+
+    if (joystick->hwdata) {
+        SDL_HIDAPI_Device *device = joystick->hwdata->device;
+
+        result = device->driver->HasJoystickLED(device, joystick);
+    }
+
+    return result;
+}
+
 static int
 HIDAPI_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1138,6 +1152,7 @@ SDL_JoystickDriver SDL_HIDAPI_JoystickDriver =
     HIDAPI_JoystickGetDeviceInstanceID,
     HIDAPI_JoystickOpen,
     HIDAPI_JoystickRumble,
+    HIDAPI_JoystickHasLED,
     HIDAPI_JoystickSetLED,
     HIDAPI_JoystickUpdate,
     HIDAPI_JoystickClose,

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

@@ -94,6 +94,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver
     SDL_bool (*UpdateDevice)(SDL_HIDAPI_Device *device);
     SDL_bool (*OpenJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
     int (*RumbleJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_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);
     void (*CloseJoystick)(SDL_HIDAPI_Device *device, SDL_Joystick *joystick);
     void (*FreeDevice)(SDL_HIDAPI_Device *device);

+ 92 - 85
src/joystick/iphoneos/SDL_sysjoystick.m

@@ -827,7 +827,7 @@ IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
 @implementation SDL_RumbleMotor {
     CHHapticEngine *engine API_AVAILABLE(ios(13.0), tvos(14.0));
     id<CHHapticPatternPlayer> player API_AVAILABLE(ios(13.0), tvos(14.0));
-	bool active;
+    bool active;
 }
 
 -(void)cleanup
@@ -845,91 +845,91 @@ IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
 -(int)setIntensity:(float)intensity
 {
     @autoreleasepool {
-		if (@available(iOS 14.0, tvOS 14.0, *)) {
-			NSError *error;
-			
-			if (self->engine == nil) {
-				return SDL_SetError("Haptics engine was stopped");
-			}
-
-			if (intensity == 0.0f) {
-				if (self->player && self->active) {
-					[self->player stopAtTime:0 error:&error];
-				}
-				self->active = false;
-				return 0;
-			}
-
-			if (self->player == nil) {
-				CHHapticEventParameter *param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
-				CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:param, nil] relativeTime:0 duration:GCHapticDurationInfinite];
-				CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error];
-				if (error != nil) {
-					return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]);
-				}
-
-				self->player = [self->engine createPlayerWithPattern:pattern error:&error];
-				if (error != nil) {
-					return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]);
-				}
-				self->active = false;
-			}
-
-			CHHapticDynamicParameter *param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0];
-			[self->player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error];
-			if (error != nil) {
-				return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]);
-			}
-
-			if (!self->active) {
-				[self->player startAtTime:0 error:&error];
-				self->active = true;
-			}
-		}
-
-		return 0;
-	}
+        if (@available(iOS 14.0, tvOS 14.0, *)) {
+            NSError *error;
+            
+            if (self->engine == nil) {
+                return SDL_SetError("Haptics engine was stopped");
+            }
+
+            if (intensity == 0.0f) {
+                if (self->player && self->active) {
+                    [self->player stopAtTime:0 error:&error];
+                }
+                self->active = false;
+                return 0;
+            }
+
+            if (self->player == nil) {
+                CHHapticEventParameter *param = [[CHHapticEventParameter alloc] initWithParameterID:CHHapticEventParameterIDHapticIntensity value:1.0f];
+                CHHapticEvent *event = [[CHHapticEvent alloc] initWithEventType:CHHapticEventTypeHapticContinuous parameters:[NSArray arrayWithObjects:param, nil] relativeTime:0 duration:GCHapticDurationInfinite];
+                CHHapticPattern *pattern = [[CHHapticPattern alloc] initWithEvents:[NSArray arrayWithObject:event] parameters:[[NSArray alloc] init] error:&error];
+                if (error != nil) {
+                    return SDL_SetError("Couldn't create haptic pattern: %s", [error.localizedDescription UTF8String]);
+                }
+
+                self->player = [self->engine createPlayerWithPattern:pattern error:&error];
+                if (error != nil) {
+                    return SDL_SetError("Couldn't create haptic player: %s", [error.localizedDescription UTF8String]);
+                }
+                self->active = false;
+            }
+
+            CHHapticDynamicParameter *param = [[CHHapticDynamicParameter alloc] initWithParameterID:CHHapticDynamicParameterIDHapticIntensityControl value:intensity relativeTime:0];
+            [self->player sendParameters:[NSArray arrayWithObject:param] atTime:0 error:&error];
+            if (error != nil) {
+                return SDL_SetError("Couldn't update haptic player: %s", [error.localizedDescription UTF8String]);
+            }
+
+            if (!self->active) {
+                [self->player startAtTime:0 error:&error];
+                self->active = true;
+            }
+        }
+
+        return 0;
+    }
 }
 
 -(id) initWithController:(GCController*)controller locality:(GCHapticsLocality)locality API_AVAILABLE(ios(14.0), tvos(14.0))
 {
     @autoreleasepool {
-		NSError *error;
-
-		self->engine = [controller.haptics createEngineWithLocality:locality];
-		if (self->engine == nil) {
-			SDL_SetError("Couldn't create haptics engine");
-			return nil;
-		}
-
-		[self->engine startAndReturnError:&error];
-		if (error != nil) {
-			SDL_SetError("Couldn't start haptics engine");
-			return nil;
-		}
-
-		__weak typeof(self) weakSelf = self;
-		self->engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
-			SDL_RumbleMotor *_this = weakSelf;
-			if (_this == nil) {
-				return;
-			}
-
-			_this->player = nil;
-			_this->engine = nil;
-		};
-		self->engine.resetHandler = ^{
-			SDL_RumbleMotor *_this = weakSelf;
-			if (_this == nil) {
-				return;
-			}
-
-			_this->player = nil;
-			[_this->engine startAndReturnError:nil];
-		};
-
-		return self;
-	}
+        NSError *error;
+
+        self->engine = [controller.haptics createEngineWithLocality:locality];
+        if (self->engine == nil) {
+            SDL_SetError("Couldn't create haptics engine");
+            return nil;
+        }
+
+        [self->engine startAndReturnError:&error];
+        if (error != nil) {
+            SDL_SetError("Couldn't start haptics engine");
+            return nil;
+        }
+
+        __weak typeof(self) weakSelf = self;
+        self->engine.stoppedHandler = ^(CHHapticEngineStoppedReason stoppedReason) {
+            SDL_RumbleMotor *_this = weakSelf;
+            if (_this == nil) {
+                return;
+            }
+
+            _this->player = nil;
+            _this->engine = nil;
+        };
+        self->engine.resetHandler = ^{
+            SDL_RumbleMotor *_this = weakSelf;
+            if (_this == nil) {
+                return;
+            }
+
+            _this->player = nil;
+            [_this->engine startAndReturnError:nil];
+        };
+
+        return self;
+    }
 }
 
 @end
@@ -960,8 +960,8 @@ IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
 
 -(void)cleanup
 {
-	[self->low_frequency_motor cleanup];
-	[self->high_frequency_motor cleanup];
+    [self->low_frequency_motor cleanup];
+    [self->high_frequency_motor cleanup];
 }
 
 @end
@@ -1008,6 +1008,12 @@ IOS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint16
 #endif
 }
 
+static SDL_bool
+IOS_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 IOS_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1044,9 +1050,9 @@ IOS_JoystickClose(SDL_Joystick * joystick)
     @autoreleasepool {
 #ifdef ENABLE_MFI_RUMBLE
         if (device->rumble) {
-			SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
+            SDL_RumbleContext *rumble = (__bridge SDL_RumbleContext *)device->rumble;
 
-			[rumble cleanup];
+            [rumble cleanup];
             CFRelease(device->rumble);
             device->rumble = NULL;
         }
@@ -1122,6 +1128,7 @@ SDL_JoystickDriver SDL_IOS_JoystickDriver =
     IOS_JoystickGetDeviceInstanceID,
     IOS_JoystickOpen,
     IOS_JoystickRumble,
+    IOS_JoystickHasLED,
     IOS_JoystickSetLED,
     IOS_JoystickUpdate,
     IOS_JoystickClose,

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

@@ -891,6 +891,12 @@ LINUX_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uint1
     return 0;
 }
 
+static SDL_bool
+LINUX_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 LINUX_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -1372,6 +1378,7 @@ SDL_JoystickDriver SDL_LINUX_JoystickDriver =
     LINUX_JoystickGetDeviceInstanceID,
     LINUX_JoystickOpen,
     LINUX_JoystickRumble,
+    LINUX_JoystickHasLED,
     LINUX_JoystickSetLED,
     LINUX_JoystickUpdate,
     LINUX_JoystickClose,

+ 8 - 0
src/joystick/virtual/SDL_virtualjoystick.c

@@ -339,6 +339,13 @@ VIRTUAL_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uin
 }
 
 
+static SDL_bool
+VIRTUAL_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
+
 static int
 VIRTUAL_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -416,6 +423,7 @@ SDL_JoystickDriver SDL_VIRTUAL_JoystickDriver =
     VIRTUAL_JoystickGetDeviceInstanceID,
     VIRTUAL_JoystickOpen,
     VIRTUAL_JoystickRumble,
+    VIRTUAL_JoystickHasLED,
     VIRTUAL_JoystickSetLED,
     VIRTUAL_JoystickUpdate,
     VIRTUAL_JoystickClose,

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

@@ -500,6 +500,12 @@ WINDOWS_JoystickRumble(SDL_Joystick * joystick, Uint16 low_frequency_rumble, Uin
     }
 }
 
+static SDL_bool
+WINDOWS_JoystickHasLED(SDL_Joystick * joystick)
+{
+    return SDL_FALSE;
+}
+
 static int
 WINDOWS_JoystickSetLED(SDL_Joystick * joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
@@ -589,6 +595,7 @@ SDL_JoystickDriver SDL_WINDOWS_JoystickDriver =
     WINDOWS_JoystickGetDeviceInstanceID,
     WINDOWS_JoystickOpen,
     WINDOWS_JoystickRumble,
+    WINDOWS_JoystickHasLED,
     WINDOWS_JoystickSetLED,
     WINDOWS_JoystickUpdate,
     WINDOWS_JoystickClose,

+ 12 - 0
test/testgamecontroller.c

@@ -193,6 +193,18 @@ loop(void *arg)
             }
         }
 
+        /* Update LED based on left thumbstick position */
+        {
+            Uint8 r, g, b;
+            Sint16 x = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTX);
+            Sint16 y = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_LEFTY);
+
+            r = (Uint8)((float)(((int)x + 32767) * 255) / 65535);
+            b = (Uint8)((float)(((int)y + 32767) * 255) / 65535);
+            g = (Uint8)((int)(r + b) / 2);
+            SDL_GameControllerSetLED(gamecontroller, r, g, b);
+        }
+
         /* Update rumble based on trigger state */
         {
             Uint16 low_frequency_rumble = SDL_GameControllerGetAxis(gamecontroller, SDL_CONTROLLER_AXIS_TRIGGERLEFT) * 2;