SDL: Added effects support for virtual controllers

From f0bc5c9cbf3d589239d18d6b29e5072a0b77d7da Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 16 May 2022 07:21:28 -0700
Subject: [PATCH] Added effects support for virtual controllers

---
 include/SDL_joystick.h                     |  6 ++++
 src/joystick/virtual/SDL_virtualjoystick.c | 41 +++++++++++++++++++++-
 test/testgamecontroller.c                  | 27 ++++++++++++++
 3 files changed, 73 insertions(+), 1 deletion(-)

diff --git a/include/SDL_joystick.h b/include/SDL_joystick.h
index 277e29a371f..9825b5be64b 100644
--- a/include/SDL_joystick.h
+++ b/include/SDL_joystick.h
@@ -352,6 +352,7 @@ extern DECLSPEC int SDLCALL SDL_JoystickAttachVirtual(SDL_JoystickType type,
  * The structure that defines an extended virtual joystick description
  *
  * The caller must zero the structure and then initialize the version with `SDL_VIRTUAL_JOYSTICK_DESC_VERSION` before passing it to SDL_JoystickAttachVirtualEx()
+ *  All other elements of this structure are optional and can be left 0.
  *
  * \sa SDL_JoystickAttachVirtualEx
  */
@@ -373,6 +374,11 @@ typedef struct SDL_VirtualJoystickDesc
 
     void *userdata;     /**< User data pointer passed to callbacks */
     void (*Update)(void *userdata); /**< Called when the joystick state should be updated */
+    void (*SetPlayerIndex)(void *userdata, int player_index); /**< Called when the player index is set */
+    int (*Rumble)(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble); /**< Implements SDL_JoystickRumble() */
+    int (*RumbleTriggers)(void *userdata, Uint16 left_rumble, Uint16 right_rumble); /**< Implements SDL_JoystickRumbleTriggers() */
+    int (*SetLED)(void *userdata, Uint8 red, Uint8 green, Uint8 blue); /**< Implements SDL_JoystickSetLED() */
+    int (*SendEffect)(void *userdata, const void *data, int size); /**< Implements SDL_JoystickSendEffect() */
 
 } SDL_VirtualJoystickDesc;
 
diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c
index 6e80d385b0f..4449d0af069 100644
--- a/src/joystick/virtual/SDL_virtualjoystick.c
+++ b/src/joystick/virtual/SDL_virtualjoystick.c
@@ -395,6 +395,11 @@ VIRTUAL_JoystickGetDevicePlayerIndex(int device_index)
 static void
 VIRTUAL_JoystickSetDevicePlayerIndex(int device_index, int player_index)
 {
+    joystick_hwdata *hwdata = VIRTUAL_HWDataForIndex(device_index);
+
+    if (hwdata && hwdata->desc.SetPlayerIndex) {
+        hwdata->desc.SetPlayerIndex(hwdata->desc.userdata, player_index);
+    }
 }
 
 
@@ -446,12 +451,22 @@ VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
 static int
 VIRTUAL_JoystickRumble(SDL_Joystick *joystick, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
 {
+    joystick_hwdata *hwdata = joystick->hwdata;
+
+    if (hwdata && hwdata->desc.Rumble) {
+        return hwdata->desc.Rumble(hwdata->desc.userdata, low_frequency_rumble, high_frequency_rumble);
+    }
     return SDL_Unsupported();
 }
 
 static int
 VIRTUAL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint16 right_rumble)
 {
+    joystick_hwdata *hwdata = joystick->hwdata;
+
+    if (hwdata && hwdata->desc.RumbleTriggers) {
+        return hwdata->desc.RumbleTriggers(hwdata->desc.userdata, left_rumble, right_rumble);
+    }
     return SDL_Unsupported();
 }
 
@@ -459,19 +474,43 @@ VIRTUAL_JoystickRumbleTriggers(SDL_Joystick *joystick, Uint16 left_rumble, Uint1
 static Uint32
 VIRTUAL_JoystickGetCapabilities(SDL_Joystick *joystick)
 {
-    return 0;
+    joystick_hwdata *hwdata = joystick->hwdata;
+    Uint32 caps = 0;
+
+    if (hwdata) {
+        if (hwdata->desc.Rumble) {
+            caps |= SDL_JOYCAP_RUMBLE;
+        }
+        if (hwdata->desc.RumbleTriggers) {
+            caps |= SDL_JOYCAP_RUMBLE_TRIGGERS;
+        }
+        if (hwdata->desc.SetLED) {
+            caps |= SDL_JOYCAP_LED;
+        }
+    }
+    return caps;
 }
 
 
 static int
 VIRTUAL_JoystickSetLED(SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue)
 {
+    joystick_hwdata *hwdata = joystick->hwdata;
+
+    if (hwdata && hwdata->desc.SetLED) {
+        return hwdata->desc.SetLED(hwdata->desc.userdata, red, green, blue);
+    }
     return SDL_Unsupported();
 }
 
 static int
 VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data, int size)
 {
+    joystick_hwdata *hwdata = joystick->hwdata;
+
+    if (hwdata && hwdata->desc.SendEffect) {
+        return hwdata->desc.SendEffect(hwdata->desc.userdata, data, size);
+    }
     return SDL_Unsupported();
 }
 
diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c
index 086a340413f..b24581598a4 100644
--- a/test/testgamecontroller.c
+++ b/test/testgamecontroller.c
@@ -312,6 +312,29 @@ static SDL_bool ShowingFront()
     return showing_front;
 }
 
+static void VirtualControllerSetPlayerIndex(void *userdata, int player_index)
+{
+    SDL_Log("Virtual Controller: player index set to %d\n", player_index);
+}
+
+static int VirtualControllerRumble(void *userdata, Uint16 low_frequency_rumble, Uint16 high_frequency_rumble)
+{
+    SDL_Log("Virtual Controller: rumble set to %d/%d\n", low_frequency_rumble, high_frequency_rumble);
+    return 0;
+}
+
+static int VirtualControllerRumbleTriggers(void *userdata, Uint16 left_rumble, Uint16 right_rumble)
+{
+    SDL_Log("Virtual Controller: trigger rumble set to %d/%d\n", left_rumble, right_rumble);
+    return 0;
+}
+
+static int VirtualControllerSetLED(void *userdata, Uint8 red, Uint8 green, Uint8 blue)
+{
+    SDL_Log("Virtual Controller: LED set to RGB %d,%d,%d\n", red, green, blue);
+    return 0;
+}
+
 static int OpenVirtualController()
 {
     SDL_VirtualJoystickDesc desc;
@@ -321,6 +344,10 @@ static int OpenVirtualController()
     desc.type = SDL_JOYSTICK_TYPE_GAMECONTROLLER;
     desc.naxes = SDL_CONTROLLER_AXIS_MAX;
     desc.nbuttons = SDL_CONTROLLER_BUTTON_MAX;
+    desc.SetPlayerIndex = VirtualControllerSetPlayerIndex;
+    desc.Rumble = VirtualControllerRumble;
+    desc.RumbleTriggers = VirtualControllerRumbleTriggers;
+    desc.SetLED = VirtualControllerSetLED;
     return SDL_JoystickAttachVirtualEx(&desc);
 }