SDL: Added ball, touchpad, and sensor support for virtual joysticks

From c1ba31118b9033f738c99e95147bb8168f0fe075 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Thu, 9 May 2024 17:36:15 -0700
Subject: [PATCH] Added ball, touchpad, and sensor support for virtual
 joysticks

Fixes https://github.com/libsdl-org/SDL/issues/9542
---
 include/SDL3/SDL_events.h                    |   2 +-
 include/SDL3/SDL_gamepad.h                   |   4 +-
 include/SDL3/SDL_joystick.h                  | 124 +++++++++-
 src/dynapi/SDL_dynapi.sym                    |   3 +
 src/dynapi/SDL_dynapi_overrides.h            |   3 +
 src/dynapi/SDL_dynapi_procs.h                |   3 +
 src/joystick/SDL_joystick.c                  |  57 +++++
 src/joystick/virtual/SDL_virtualjoystick.c   | 243 +++++++++++++++++--
 src/joystick/virtual/SDL_virtualjoystick_c.h |  42 +++-
 test/gamepadutils.c                          |  14 ++
 test/gamepadutils.h                          |   1 +
 test/testcontroller.c                        |  37 +++
 12 files changed, 493 insertions(+), 40 deletions(-)

diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index 1ef15bb3cabf8..6757f036e7760 100644
--- a/include/SDL3/SDL_events.h
+++ b/include/SDL3/SDL_events.h
@@ -48,7 +48,7 @@ extern "C" {
 
 /* General keyboard/mouse state definitions */
 #define SDL_RELEASED    0
-#define SDL_PRESSED 1
+#define SDL_PRESSED     1
 
 /**
  * The types of events that can be delivered.
diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index f3010e8d87fdc..36904e478756b 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -1240,8 +1240,8 @@ extern DECLSPEC int SDLCALL SDL_GetNumGamepadTouchpadFingers(SDL_Gamepad *gamepa
  * \param touchpad a touchpad
  * \param finger a finger
  * \param state filled with state
- * \param x filled with x position
- * \param y filled with y position
+ * \param x filled with x position, normalized 0 to 1, with the origin in the upper left
+ * \param y filled with y position, normalized 0 to 1, with the origin in the upper left
  * \param pressure filled with pressure value
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
diff --git a/include/SDL3/SDL_joystick.h b/include/SDL3/SDL_joystick.h
index 9c759f063b4f6..2a95438535b67 100644
--- a/include/SDL3/SDL_joystick.h
+++ b/include/SDL3/SDL_joystick.h
@@ -44,6 +44,7 @@
 #include <SDL3/SDL_mutex.h>
 #include <SDL3/SDL_power.h>
 #include <SDL3/SDL_properties.h>
+#include <SDL3/SDL_sensor.h>
 
 #include <SDL3/SDL_begin_code.h>
 /* Set up for C function definitions, even when using C++ */
@@ -391,28 +392,62 @@ extern DECLSPEC SDL_Joystick *SDLCALL SDL_GetJoystickFromInstanceID(SDL_Joystick
 extern DECLSPEC SDL_Joystick *SDLCALL SDL_GetJoystickFromPlayerIndex(int player_index);
 
 /**
- * The structure that defines an extended virtual joystick description
+ * The structure that describes a virtual joystick touchpad.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_VirtualJoystickDesc
+ */
+typedef struct SDL_VirtualJoystickTouchpadDesc
+{
+    Uint16 nfingers;    /**< the number of simultaneous fingers on this touchpad */
+    Uint16 padding[3];
+} SDL_VirtualJoystickTouchpadDesc;
+
+/**
+ * The structure that describes a virtual joystick sensor.
+ *
+ * \since This struct is available since SDL 3.0.0.
+ *
+ * \sa SDL_VirtualJoystickDesc
+ */
+typedef struct SDL_VirtualJoystickSensorDesc
+{
+    SDL_SensorType type;    /**< the type of this sensor */
+    float rate;             /**< the update frequency of this sensor, may be 0.0f */
+} SDL_VirtualJoystickSensorDesc;
+
+/**
+ * The structure that describes a virtual joystick.
  *
  * All elements of this structure are optional and can be left 0.
  *
  * \since This struct is available since SDL 3.0.0.
  *
  * \sa SDL_AttachVirtualJoystick
+ * \sa SDL_VirtualJoystickSensorDesc
+ * \sa SDL_VirtualJoystickTouchpadDesc
  */
 typedef struct SDL_VirtualJoystickDesc
 {
     Uint16 type;        /**< `SDL_JoystickType` */
+    Uint16 padding;     /**< unused */
+    Uint16 vendor_id;   /**< the USB vendor ID of this joystick */
+    Uint16 product_id;  /**< the USB product ID of this joystick */
     Uint16 naxes;       /**< the number of axes on this joystick */
     Uint16 nbuttons;    /**< the number of buttons on this joystick */
+    Uint16 nballs;      /**< the number of balls on this joystick */
     Uint16 nhats;       /**< the number of hats on this joystick */
-    Uint16 vendor_id;   /**< the USB vendor ID of this joystick */
-    Uint16 product_id;  /**< the USB product ID of this joystick */
-    Uint16 padding;     /**< unused */
+    Uint16 ntouchpads;  /**< the number of touchpads on this joystick, requires `touchpads` to point at valid descriptions */
+    Uint16 nsensors;    /**< the number of sensors on this joystick, requires `sensors` to point at valid descriptions */
+    Uint16 padding2[2]; /**< unused */
     Uint32 button_mask; /**< A mask of which buttons are valid for this controller
-                             e.g. (1u << SDL_GAMEPAD_BUTTON_SOUTH) */
+                             e.g. (1 << SDL_GAMEPAD_BUTTON_SOUTH) */
     Uint32 axis_mask;   /**< A mask of which axes are valid for this controller
-                             e.g. (1u << SDL_GAMEPAD_AXIS_LEFTX) */
+                             e.g. (1 << SDL_GAMEPAD_AXIS_LEFTX) */
     const char *name;   /**< the name of the joystick */
+    const SDL_VirtualJoystickTouchpadDesc *touchpads;   /**< A pointer to an array of touchpad descriptions, required if `ntouchpads` is > 0 */
+    const SDL_VirtualJoystickSensorDesc *sensors;       /**< A pointer to an array of sensor descriptions, required if `nsensors` is > 0 */
 
     void *userdata;     /**< User data pointer passed to callbacks */
     void (SDLCALL *Update)(void *userdata); /**< Called when the joystick state should be updated */
@@ -421,6 +456,7 @@ typedef struct SDL_VirtualJoystickDesc
     int (SDLCALL *RumbleTriggers)(void *userdata, Uint16 left_rumble, Uint16 right_rumble); /**< Implements SDL_RumbleJoystickTriggers() */
     int (SDLCALL *SetLED)(void *userdata, Uint8 red, Uint8 green, Uint8 blue); /**< Implements SDL_SetJoystickLED() */
     int (SDLCALL *SendEffect)(void *userdata, const void *data, int size); /**< Implements SDL_SendJoystickEffect() */
+    int (SDLCALL *SetSensorsEnabled)(void *userdata, SDL_bool enabled); /**< Implements SDL_SetGamepadSensorEnabled() */
 } SDL_VirtualJoystickDesc;
 
 /**
@@ -461,7 +497,7 @@ extern DECLSPEC int SDLCALL SDL_DetachVirtualJoystick(SDL_JoystickID instance_id
 extern DECLSPEC SDL_bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_id);
 
 /**
- * Set values on an opened, virtual-joystick's axis.
+ * Set the state of an axis on an opened virtual joystick.
  *
  * Please note that values set here will not be applied until the next call to
  * SDL_UpdateJoysticks, which can either be called directly, or can be called
@@ -474,7 +510,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_i
  * `SDL_JOYSTICK_AXIS_MIN`.
  *
  * \param joystick the virtual joystick on which to set state.
- * \param axis the specific axis on the virtual joystick to set.
+ * \param axis the index of the axis on the virtual joystick to update.
  * \param value the new value for the specified axis.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
@@ -484,7 +520,7 @@ extern DECLSPEC SDL_bool SDLCALL SDL_IsJoystickVirtual(SDL_JoystickID instance_i
 extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value);
 
 /**
- * Set values on an opened, virtual-joystick's button.
+ * Generate ball motion on an opened virtual joystick.
  *
  * Please note that values set here will not be applied until the next call to
  * SDL_UpdateJoysticks, which can either be called directly, or can be called
@@ -493,7 +529,27 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, i
  * SDL_WaitEvent.
  *
  * \param joystick the virtual joystick on which to set state.
- * \param button the specific button on the virtual joystick to set.
+ * \param ball the index of the ball on the virtual joystick to update.
+ * \param xrel the relative motion on the X axis.
+ * \param yrel the relative motion on the Y axis.
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel);
+
+/**
+ * Set the state of a button on an opened virtual joystick.
+ *
+ * Please note that values set here will not be applied until the next call to
+ * SDL_UpdateJoysticks, which can either be called directly, or can be called
+ * indirectly through various other SDL APIs, including, but not limited to
+ * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout,
+ * SDL_WaitEvent.
+ *
+ * \param joystick the virtual joystick on which to set state.
+ * \param button the index of the button on the virtual joystick to update.
  * \param value the new value for the specified button.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
@@ -503,7 +559,7 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, i
 extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, Uint8 value);
 
 /**
- * Set values on an opened, virtual-joystick's hat.
+ * Set the state of a hat on an opened virtual joystick.
  *
  * Please note that values set here will not be applied until the next call to
  * SDL_UpdateJoysticks, which can either be called directly, or can be called
@@ -512,7 +568,7 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick,
  * SDL_WaitEvent.
  *
  * \param joystick the virtual joystick on which to set state.
- * \param hat the specific hat on the virtual joystick to set.
+ * \param hat the index of the hat on the virtual joystick to update.
  * \param value the new value for the specified hat.
  * \returns 0 on success or a negative error code on failure; call
  *          SDL_GetError() for more information.
@@ -521,6 +577,50 @@ extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualButton(SDL_Joystick *joystick,
  */
 extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value);
 
+/**
+ * Set touchpad finger state on an opened virtual joystick.
+ *
+ * Please note that values set here will not be applied until the next call to
+ * SDL_UpdateJoysticks, which can either be called directly, or can be called
+ * indirectly through various other SDL APIs, including, but not limited to
+ * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout,
+ * SDL_WaitEvent.
+ *
+ * \param joystick the virtual joystick on which to set state.
+ * \param touchpad the index of the touchpad on the virtual joystick to update.
+ * \param finger the index of the finger on the touchpad to set.
+ * \param state `SDL_PRESSED` if the finger is pressed, `SDL_RELEASED` if the finger is released
+ * \param x the x coordinate of the finger on the touchpad, normalized 0 to 1, with the origin in the upper left
+ * \param y the y coordinate of the finger on the touchpad, normalized 0 to 1, with the origin in the upper left
+ * \param pressure the pressure of the finger
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure);
+
+/**
+ * Send a sensor update for an opened virtual joystick.
+ *
+ * Please note that values set here will not be applied until the next call to
+ * SDL_UpdateJoysticks, which can either be called directly, or can be called
+ * indirectly through various other SDL APIs, including, but not limited to
+ * the following: SDL_PollEvent, SDL_PumpEvents, SDL_WaitEventTimeout,
+ * SDL_WaitEvent.
+ *
+ * \param joystick the virtual joystick on which to set state.
+ * \param type the type of the sensor on the virtual joystick to update.
+ * \param sensor_timestamp a 64-bit timestamp in nanoseconds associated with the sensor reading
+ * \param data the data associated with the sensor reading
+ * \param num_values the number of values pointed to by `data`
+ * \returns 0 on success or a negative error code on failure; call
+ *          SDL_GetError() for more information.
+ *
+ * \since This function is available since SDL 3.0.0.
+ */
+extern DECLSPEC int SDLCALL SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values);
+
 /**
  * Get the properties associated with a joystick.
  *
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index 0eec501b1e16c..919998f84f984 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -680,6 +680,7 @@ SDL3_0.0.0 {
     SDL_SeekIO;
     SDL_SendGamepadEffect;
     SDL_SendJoystickEffect;
+    SDL_SendJoystickVirtualSensorData;
     SDL_SetAssertionHandler;
     SDL_SetAudioPostmixCallback;
     SDL_SetAudioStreamFormat;
@@ -708,8 +709,10 @@ SDL3_0.0.0 {
     SDL_SetJoystickLED;
     SDL_SetJoystickPlayerIndex;
     SDL_SetJoystickVirtualAxis;
+    SDL_SetJoystickVirtualBall;
     SDL_SetJoystickVirtualButton;
     SDL_SetJoystickVirtualHat;
+    SDL_SetJoystickVirtualTouchpad;
     SDL_SetLogOutputFunction;
     SDL_SetMainReady;
     SDL_SetMemoryFunctions;
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 7acba407509c9..335134bfbb7b2 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -705,6 +705,7 @@
 #define SDL_SeekIO SDL_SeekIO_REAL
 #define SDL_SendGamepadEffect SDL_SendGamepadEffect_REAL
 #define SDL_SendJoystickEffect SDL_SendJoystickEffect_REAL
+#define SDL_SendJoystickVirtualSensorData SDL_SendJoystickVirtualSensorData_REAL
 #define SDL_SetAssertionHandler SDL_SetAssertionHandler_REAL
 #define SDL_SetAudioPostmixCallback SDL_SetAudioPostmixCallback_REAL
 #define SDL_SetAudioStreamFormat SDL_SetAudioStreamFormat_REAL
@@ -732,8 +733,10 @@
 #define SDL_SetJoystickLED SDL_SetJoystickLED_REAL
 #define SDL_SetJoystickPlayerIndex SDL_SetJoystickPlayerIndex_REAL
 #define SDL_SetJoystickVirtualAxis SDL_SetJoystickVirtualAxis_REAL
+#define SDL_SetJoystickVirtualBall SDL_SetJoystickVirtualBall_REAL
 #define SDL_SetJoystickVirtualButton SDL_SetJoystickVirtualButton_REAL
 #define SDL_SetJoystickVirtualHat SDL_SetJoystickVirtualHat_REAL
+#define SDL_SetJoystickVirtualTouchpad SDL_SetJoystickVirtualTouchpad_REAL
 #define SDL_SetLogOutputFunction SDL_SetLogOutputFunction_REAL
 #define SDL_SetMainReady SDL_SetMainReady_REAL
 #define SDL_SetMemoryFunctions SDL_SetMemoryFunctions_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index ba3e936bb537a..4a9047bc26a2b 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -726,6 +726,7 @@ SDL_DYNAPI_PROC(SDL_bool,SDL_ScreenSaverEnabled,(void),(),return)
 SDL_DYNAPI_PROC(Sint64,SDL_SeekIO,(SDL_IOStream *a, Sint64 b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SendGamepadEffect,(SDL_Gamepad *a, const void *b, int c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SendJoystickEffect,(SDL_Joystick *a, const void *b, int c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SendJoystickVirtualSensorData,(SDL_Joystick *a, SDL_SensorType b, Uint64 c, const float *d, int e),(a,b,c,d,e),return)
 SDL_DYNAPI_PROC(void,SDL_SetAssertionHandler,(SDL_AssertionHandler a, void *b),(a,b),)
 SDL_DYNAPI_PROC(int,SDL_SetAudioPostmixCallback,(SDL_AudioDeviceID a, SDL_AudioPostmixCallback b, void *c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetAudioStreamFormat,(SDL_AudioStream *a, const SDL_AudioSpec *b, const SDL_AudioSpec *c),(a,b,c),return)
@@ -752,8 +753,10 @@ SDL_DYNAPI_PROC(void,SDL_SetJoystickEventsEnabled,(SDL_bool a),(a),)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickLED,(SDL_Joystick *a, Uint8 b, Uint8 c, Uint8 d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickPlayerIndex,(SDL_Joystick *a, int b),(a,b),return)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualAxis,(SDL_Joystick *a, int b, Sint16 c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualBall,(SDL_Joystick *a, int b, Sint16 c, Sint16 d),(a,b,c,d),return)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualButton,(SDL_Joystick *a, int b, Uint8 c),(a,b,c),return)
 SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualHat,(SDL_Joystick *a, int b, Uint8 c),(a,b,c),return)
+SDL_DYNAPI_PROC(int,SDL_SetJoystickVirtualTouchpad,(SDL_Joystick *a, int b, int c, Uint8 d, float e, float f, float g),(a,b,c,d,e,f,g),return)
 SDL_DYNAPI_PROC(void,SDL_SetLogOutputFunction,(SDL_LogOutputFunction a, void *b),(a,b),)
 SDL_DYNAPI_PROC(void,SDL_SetMainReady,(void),(),)
 SDL_DYNAPI_PROC(int,SDL_SetMemoryFunctions,(SDL_malloc_func a, SDL_calloc_func b, SDL_realloc_func c, SDL_free_func d),(a,b,c,d),return)
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index fbd67bbd7c506..1b7115fa051aa 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -1235,6 +1235,25 @@ int SDL_SetJoystickVirtualAxis(SDL_Joystick *joystick, int axis, Sint16 value)
     return retval;
 }
 
+int SDL_SetJoystickVirtualBall(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel)
+{
+    int retval;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, -1);
+
+#ifdef SDL_JOYSTICK_VIRTUAL
+        retval = SDL_SetJoystickVirtualBallInner(joystick, ball, xrel, yrel);
+#else
+        retval = SDL_SetError("SDL not built with virtual-joystick support");
+#endif
+    }
+    SDL_UnlockJoysticks();
+
+    return retval;
+}
+
 int SDL_SetJoystickVirtualButton(SDL_Joystick *joystick, int button, Uint8 value)
 {
     int retval;
@@ -1273,6 +1292,44 @@ int SDL_SetJoystickVirtualHat(SDL_Joystick *joystick, int hat, Uint8 value)
     return retval;
 }
 
+int SDL_SetJoystickVirtualTouchpad(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure)
+{
+    int retval;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, -1);
+
+#ifdef SDL_JOYSTICK_VIRTUAL
+        retval = SDL_SetJoystickVirtualTouchpadInner(joystick, touchpad, finger, state, x, y, pressure);
+#else
+        retval = SDL_SetError("SDL not built with virtual-joystick support");
+#endif
+    }
+    SDL_UnlockJoysticks();
+
+    return retval;
+}
+
+int SDL_SendJoystickVirtualSensorData(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)
+{
+    int retval;
+
+    SDL_LockJoysticks();
+    {
+        CHECK_JOYSTICK_MAGIC(joystick, -1);
+
+#ifdef SDL_JOYSTICK_VIRTUAL
+        retval = SDL_SendJoystickVirtualSensorDataInner(joystick, type, sensor_timestamp, data, num_values);
+#else
+        retval = SDL_SetError("SDL not built with virtual-joystick support");
+#endif
+    }
+    SDL_UnlockJoysticks();
+
+    return retval;
+}
+
 /*
  * Checks to make sure the joystick is valid.
  */
diff --git a/src/joystick/virtual/SDL_virtualjoystick.c b/src/joystick/virtual/SDL_virtualjoystick.c
index f6e1930819831..6165badde4d43 100644
--- a/src/joystick/virtual/SDL_virtualjoystick.c
+++ b/src/joystick/virtual/SDL_virtualjoystick.c
@@ -102,6 +102,26 @@ static void VIRTUAL_FreeHWData(joystick_hwdata *hwdata)
         SDL_free(hwdata->hats);
         hwdata->hats = NULL;
     }
+    if (hwdata->balls) {
+        SDL_free(hwdata->balls);
+        hwdata->balls = NULL;
+    }
+    if (hwdata->touchpads) {
+        for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
+            SDL_free(hwdata->touchpads[i].fingers);
+            hwdata->touchpads[i].fingers = NULL;
+        }
+        SDL_free(hwdata->touchpads);
+        hwdata->touchpads = NULL;
+    }
+    if (hwdata->sensors) {
+        SDL_free(hwdata->sensors);
+        hwdata->sensors = NULL;
+    }
+    if (hwdata->sensor_events) {
+        SDL_free(hwdata->sensor_events);
+        hwdata->sensor_events = NULL;
+    }
     SDL_free(hwdata);
 }
 
@@ -123,7 +143,9 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des
         VIRTUAL_FreeHWData(hwdata);
         return 0;
     }
-    SDL_memcpy(&hwdata->desc, desc, sizeof(*desc));
+    SDL_copyp(&hwdata->desc, desc);
+    hwdata->desc.touchpads = NULL;
+    hwdata->desc.sensors = NULL;
 
     if (hwdata->desc.name) {
         name = hwdata->desc.name;
@@ -203,7 +225,7 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des
 
     /* Allocate fields for different control-types */
     if (hwdata->desc.naxes > 0) {
-        hwdata->axes = (Sint16 *)SDL_calloc(hwdata->desc.naxes, sizeof(Sint16));
+        hwdata->axes = (Sint16 *)SDL_calloc(hwdata->desc.naxes, sizeof(*hwdata->axes));
         if (!hwdata->axes) {
             VIRTUAL_FreeHWData(hwdata);
             return 0;
@@ -218,19 +240,64 @@ SDL_JoystickID SDL_JoystickAttachVirtualInner(const SDL_VirtualJoystickDesc *des
         }
     }
     if (hwdata->desc.nbuttons > 0) {
-        hwdata->buttons = (Uint8 *)SDL_calloc(hwdata->desc.nbuttons, sizeof(Uint8));
+        hwdata->buttons = (Uint8 *)SDL_calloc(hwdata->desc.nbuttons, sizeof(*hwdata->buttons));
         if (!hwdata->buttons) {
             VIRTUAL_FreeHWData(hwdata);
             return 0;
         }
     }
     if (hwdata->desc.nhats > 0) {
-        hwdata->hats = (Uint8 *)SDL_calloc(hwdata->desc.nhats, sizeof(Uint8));
+        hwdata->hats = (Uint8 *)SDL_calloc(hwdata->desc.nhats, sizeof(*hwdata->hats));
         if (!hwdata->hats) {
             VIRTUAL_FreeHWData(hwdata);
             return 0;
         }
     }
+    if (hwdata->desc.nballs > 0) {
+        hwdata->balls = (SDL_JoystickBallData *)SDL_calloc(hwdata->desc.nballs, sizeof(*hwdata->balls));
+        if (!hwdata->balls) {
+            VIRTUAL_FreeHWData(hwdata);
+            return 0;
+        }
+    }
+    if (hwdata->desc.ntouchpads > 0) {
+        if (!desc->touchpads) {
+            VIRTUAL_FreeHWData(hwdata);
+            SDL_SetError("desc missing touchpad descriptions");
+            return 0;
+        }
+        hwdata->touchpads = (SDL_JoystickTouchpadInfo *)SDL_calloc(hwdata->desc.ntouchpads, sizeof(*hwdata->touchpads));
+        if (!hwdata->touchpads) {
+            VIRTUAL_FreeHWData(hwdata);
+            return 0;
+        }
+        for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
+            const SDL_VirtualJoystickTouchpadDesc *touchpad_desc = &desc->touchpads[i];
+            hwdata->touchpads[i].nfingers = touchpad_desc->nfingers;
+            hwdata->touchpads[i].fingers = (SDL_JoystickTouchpadFingerInfo *)SDL_calloc(touchpad_desc->nfingers, sizeof(*hwdata->touchpads[i].fingers));
+            if (!hwdata->touchpads[i].fingers) {
+                VIRTUAL_FreeHWData(hwdata);
+                return 0;
+            }
+        }
+    }
+    if (hwdata->desc.nsensors > 0) {
+        if (!desc->sensors) {
+            VIRTUAL_FreeHWData(hwdata);
+            SDL_SetError("desc missing sensor descriptions");
+            return 0;
+        }
+        hwdata->sensors = (SDL_JoystickSensorInfo *)SDL_calloc(hwdata->desc.nsensors, sizeof(*hwdata->sensors));
+        if (!hwdata->sensors) {
+            VIRTUAL_FreeHWData(hwdata);
+            return 0;
+        }
+        for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) {
+            const SDL_VirtualJoystickSensorDesc *sensor_desc = &desc->sensors[i];
+            hwdata->sensors[i].type = sensor_desc->type;
+            hwdata->sensors[i].rate = sensor_desc->rate;
+        }
+    }
 
     /* Allocate an instance ID for this device */
     hwdata->instance_id = SDL_GetNextObjectID();
@@ -268,17 +335,40 @@ int SDL_SetJoystickVirtualAxisInner(SDL_Joystick *joystick, int axis, Sint16 val
     SDL_AssertJoysticksLocked();
 
     if (!joystick || !joystick->hwdata) {
-        SDL_UnlockJoysticks();
         return SDL_SetError("Invalid joystick");
     }
 
     hwdata = (joystick_hwdata *)joystick->hwdata;
     if (axis < 0 || axis >= hwdata->desc.naxes) {
-        SDL_UnlockJoysticks();
         return SDL_SetError("Invalid axis index");
     }
 
     hwdata->axes[axis] = value;
+    hwdata->changes |= AXES_CHANGED;
+
+    return 0;
+}
+
+int SDL_SetJoystickVirtualBallInner(SDL_Joystick *joystick, int ball, Sint16 xrel, Sint16 yrel)
+{
+    joystick_hwdata *hwdata;
+
+    SDL_AssertJoysticksLocked();
+
+    if (!joystick || !joystick->hwdata) {
+        return SDL_SetError("Invalid joystick");
+    }
+
+    hwdata = (joystick_hwdata *)joystick->hwdata;
+    if (ball < 0 || ball >= hwdata->desc.nballs) {
+        return SDL_SetError("Invalid ball index");
+    }
+
+    hwdata->balls[ball].dx += xrel;
+    hwdata->balls[ball].dx = SDL_clamp(hwdata->balls[ball].dx, SDL_MIN_SINT16, SDL_MAX_SINT16);
+    hwdata->balls[ball].dy += yrel;
+    hwdata->balls[ball].dy = SDL_clamp(hwdata->balls[ball].dy, SDL_MIN_SINT16, SDL_MAX_SINT16);
+    hwdata->changes |= BALLS_CHANGED;
 
     return 0;
 }
@@ -290,17 +380,16 @@ int SDL_SetJoystickVirtualButtonInner(SDL_Joystick *joystick, int button, Uint8
     SDL_AssertJoysticksLocked();
 
     if (!joystick || !joystick->hwdata) {
-        SDL_UnlockJoysticks();
         return SDL_SetError("Invalid joystick");
     }
 
     hwdata = (joystick_hwdata *)joystick->hwdata;
     if (button < 0 || button >= hwdata->desc.nbuttons) {
-        SDL_UnlockJoysticks();
         return SDL_SetError("Invalid button index");
     }
 
     hwdata->buttons[button] = value;
+    hwdata->changes |= BUTTONS_CHANGED;
 
     return 0;
 }
@@ -312,17 +401,74 @@ int SDL_SetJoystickVirtualHatInner(SDL_Joystick *joystick, int hat, Uint8 value)
     SDL_AssertJoysticksLocked();
 
     if (!joystick || !joystick->hwdata) {
-        SDL_UnlockJoysticks();
         return SDL_SetError("Invalid joystick");
     }
 
     hwdata = (joystick_hwdata *)joystick->hwdata;
     if (hat < 0 || hat >= hwdata->desc.nhats) {
-        SDL_UnlockJoysticks();
         return SDL_SetError("Invalid hat index");
     }
 
     hwdata->hats[hat] = value;
+    hwdata->changes |= HATS_CHANGED;
+
+    return 0;
+}
+
+int SDL_SetJoystickVirtualTouchpadInner(SDL_Joystick *joystick, int touchpad, int finger, Uint8 state, float x, float y, float pressure)
+{
+    joystick_hwdata *hwdata;
+
+    SDL_AssertJoysticksLocked();
+
+    if (!joystick || !joystick->hwdata) {
+        return SDL_SetError("Invalid joystick");
+    }
+
+    hwdata = (joystick_hwdata *)joystick->hwdata;
+    if (touchpad < 0 || touchpad >= hwdata->desc.ntouchpads) {
+        return SDL_SetError("Invalid touchpad index");
+    }
+    if (finger < 0 || finger >= hwdata->touchpads[touchpad].nfingers) {
+        return SDL_SetError("Invalid finger index");
+    }
+
+    SDL_JoystickTouchpadFingerInfo *info = &hwdata->touchpads[touchpad].fingers[finger];
+    info->state = state;
+    info->x = x;
+    info->y = y;
+    info->pressure = pressure;
+    hwdata->changes |= TOUCHPADS_CHANGED;
+
+    return 0;
+}
+
+int SDL_SendJoystickVirtualSensorDataInner(SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values)
+{
+    joystick_hwdata *hwdata;
+
+    SDL_AssertJoysticksLocked();
+
+    if (!joystick || !joystick->hwdata) {
+        return SDL_SetError("Invalid joystick");
+    }
+
+    hwdata = (joystick_hwdata *)joystick->hwdata;
+    if (hwdata->num_sensor_events == hwdata->max_sensor_events) {
+        int new_max_sensor_events = (hwdata->max_sensor_events + 1);
+        VirtualSensorEvent *sensor_events = (VirtualSensorEvent *)SDL_realloc(hwdata->sensor_events, new_max_sensor_events * sizeof(*sensor_events));
+        if (!sensor_events) {
+            return -1;
+        }
+        hwdata->sensor_events = sensor_events;
+        hwdata->max_sensor_events = hwdata->max_sensor_events;
+    }
+
+    VirtualSensorEvent *event = &hwdata->sensor_events[hwdata->num_sensor_events++];
+    event->type = type;
+    event->sensor_timestamp = sensor_timestamp;
+    event->num_values = SDL_min(num_values, SDL_arraysize(event->data));
+    SDL_memcpy(event->data, data, (event->num_values * sizeof(*event->data)));
 
     return 0;
 }
@@ -424,6 +570,15 @@ static int VIRTUAL_JoystickOpen(SDL_Joystick *joystick, int device_index)
     joystick->nhats = hwdata->desc.nhats;
     hwdata->joystick = joystick;
 
+    for (Uint16 i = 0; i < hwdata->desc.ntouchpads; ++i) {
+        const SDL_JoystickTouchpadInfo *touchpad = &hwdata->touchpads[i];
+        SDL_PrivateJoystickAddTouchpad(joystick, touchpad->nfingers);
+    }
+    for (Uint16 i = 0; i < hwdata->desc.nsensors; ++i) {
+        const SDL_JoystickSensorInfo *sensor = &hwdata->sensors[i];
+        SDL_PrivateJoystickAddSensor(joystick, sensor->type, sensor->rate);
+    }
+
     if (hwdata->desc.SetLED) {
         SDL_SetBooleanProperty(SDL_GetJoystickProperties(joystick), SDL_PROP_JOYSTICK_CAP_RGB_LED_BOOLEAN, SDL_TRUE);
     }
@@ -518,13 +673,30 @@ static int VIRTUAL_JoystickSendEffect(SDL_Joystick *joystick, const void *data,
 
 static int VIRTUAL_JoystickSetSensorsEnabled(SDL_Joystick *joystick, SDL_bool enabled)
 {
-    return SDL_Unsupported();
+    int result;
+
+    SDL_AssertJoysticksLocked();
+
+    if (joystick->hwdata) {
+        joystick_hwdata *hwdata = joystick->hwdata;
+        if (hwdata->desc.SetSensorsEnabled) {
+            result = hwdata->desc.SetSensorsEnabled(hwdata->desc.userdata, enabled);
+        } else {
+            result = 0;
+        }
+        if (result == 0) {
+            hwdata->sensors_enabled = enabled;
+        }
+    } else {
+        result = SDL_SetError("SetSensorsEnabled failed, device disconnected");
+    }
+
+    return result;
 }
 
 static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick)
 {
     joystick_hwdata *hwdata;
-    Uint8 i;
     Uint64 timestamp = SDL_GetTicksNS();
 
     SDL_AssertJoysticksLocked();
@@ -542,15 +714,50 @@ static void VIRTUAL_JoystickUpdate(SDL_Joystick *joystick)
         hwdata->desc.Update(hwdata->desc.userdata);
     }
 
-    for (i = 0; i < hwdata->desc.naxes; ++i) {
-        SDL_SendJoystickAxis(timestamp, joystick, i, hwdata->axes[i]);
+    if (hwdata->changes & AXES_CHANGED) {
+        for (Uint16 i = 0; i < hwdata->desc.naxes; ++i) {
+            SDL_SendJoystickAxis(timestamp, joystick, i, hwdata->axes[i]);
+        }
+    }
+    if (hwdata->changes & BALLS_CHANGED) {
+        for (Uint16 i = 0; i < hwdata->desc.nballs; ++i) {
+            SDL_JoystickBallData *ball = &hwdata->balls[i];
+            if (ball->dx || ball->dy) {
+                SDL_SendJoystickBall(timestamp, joystick, i, ball->dx, ball->dy);
+                ball->dx = 0;
+                ball->dy = 0;
+            }
+        }
+    }
+    if (hwdata->changes & BUTTONS_CHANGED) {
+        for (Uint16 i = 0; i < hwdata->desc.nbuttons; ++i) {
+            SDL_SendJoystickButton(timestamp, joystick, i, hwdata->buttons[i]);
+        }
     }
-    for (i = 0; i < hwdata->desc.nbuttons; ++i) {
-        SDL_SendJoystickButton(timestamp, joystick, i, hwdata->buttons[i]);
+    if (hwdata->changes & HATS_CHANGED) {
+        for (Uint16 i = 0; i < hwdata->desc.nhats; ++i) {
+            SDL_SendJoystickHat(timestamp, joystick, i, h

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