Просмотр исходного кода

Add capacitive sense gamepad events (#15627)

ceski 1 месяц назад
Родитель
Сommit
5b98c1cc2f

+ 20 - 0
include/SDL3/SDL_events.h

@@ -212,6 +212,8 @@ typedef enum SDL_EventType
     SDL_EVENT_GAMEPAD_SENSOR_UPDATE,        /**< Gamepad sensor was updated */
     SDL_EVENT_GAMEPAD_SENSOR_UPDATE,        /**< Gamepad sensor was updated */
     SDL_EVENT_GAMEPAD_UPDATE_COMPLETE,      /**< Gamepad update is complete */
     SDL_EVENT_GAMEPAD_UPDATE_COMPLETE,      /**< Gamepad update is complete */
     SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED,  /**< Gamepad Steam handle has changed */
     SDL_EVENT_GAMEPAD_STEAM_HANDLE_UPDATED,  /**< Gamepad Steam handle has changed */
+    SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH,       /**< Gamepad capsense was touched */
+    SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE,     /**< Gamepad capsense was released */
 
 
     /* Touch events */
     /* Touch events */
     SDL_EVENT_FINGER_DOWN      = 0x700,
     SDL_EVENT_FINGER_DOWN      = 0x700,
@@ -711,6 +713,23 @@ typedef struct SDL_GamepadSensorEvent
     Uint64 sensor_timestamp; /**< The timestamp of the sensor reading in nanoseconds, not necessarily synchronized with the system clock */
     Uint64 sensor_timestamp; /**< The timestamp of the sensor reading in nanoseconds, not necessarily synchronized with the system clock */
 } SDL_GamepadSensorEvent;
 } SDL_GamepadSensorEvent;
 
 
+/**
+ * Gamepad capsense event structure (event.gcapsense.*)
+ *
+ * \since This struct is available since SDL 3.6.0.
+ */
+typedef struct SDL_GamepadCapSenseEvent
+{
+    SDL_EventType type;     /**< SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH or SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE */
+    Uint32 reserved;
+    Uint64 timestamp;       /**< In nanoseconds, populated using SDL_GetTicksNS() */
+    SDL_JoystickID which;   /**< The joystick instance id */
+    Uint8 capsense;         /**< The capsense type (SDL_GamepadCapSenseType) */
+    bool down;              /**< true if the capsense is touched */
+    Uint8 padding1;
+    Uint8 padding2;
+} SDL_GamepadCapSenseEvent;
+
 /**
 /**
  * Audio device event structure (event.adevice.*)
  * Audio device event structure (event.adevice.*)
  *
  *
@@ -1040,6 +1059,7 @@ typedef union SDL_Event
     SDL_GamepadButtonEvent gbutton;         /**< Gamepad button event data */
     SDL_GamepadButtonEvent gbutton;         /**< Gamepad button event data */
     SDL_GamepadTouchpadEvent gtouchpad;     /**< Gamepad touchpad event data */
     SDL_GamepadTouchpadEvent gtouchpad;     /**< Gamepad touchpad event data */
     SDL_GamepadSensorEvent gsensor;         /**< Gamepad sensor event data */
     SDL_GamepadSensorEvent gsensor;         /**< Gamepad sensor event data */
+    SDL_GamepadCapSenseEvent gcapsense;     /**< Gamepad capsense event data */
     SDL_AudioDeviceEvent adevice;           /**< Audio device event data */
     SDL_AudioDeviceEvent adevice;           /**< Audio device event data */
     SDL_CameraDeviceEvent cdevice;          /**< Camera device event data */
     SDL_CameraDeviceEvent cdevice;          /**< Camera device event data */
     SDL_SensorEvent sensor;                 /**< Sensor event data */
     SDL_SensorEvent sensor;                 /**< Sensor event data */

+ 48 - 0
include/SDL3/SDL_gamepad.h

@@ -231,6 +231,24 @@ typedef enum SDL_GamepadAxis
     SDL_GAMEPAD_AXIS_COUNT
     SDL_GAMEPAD_AXIS_COUNT
 } SDL_GamepadAxis;
 } SDL_GamepadAxis;
 
 
+/**
+ * The list of capsense types on a gamepad
+ *
+ * \since This enum is available since SDL 3.6.0.
+ *
+ * \sa SDL_GamepadHasCapSense
+ * \sa SDL_GetGamepadCapSense
+ */
+typedef enum SDL_GamepadCapSenseType
+{
+    SDL_GAMEPAD_CAPSENSE_INVALID = -1,
+    SDL_GAMEPAD_CAPSENSE_LEFT_STICK,    /**< Activated by touching the top of the left thumbstick */
+    SDL_GAMEPAD_CAPSENSE_RIGHT_STICK,   /**< Activated by touching the top of the right thumbstick */
+    SDL_GAMEPAD_CAPSENSE_LEFT_GRIP,     /**< Activated by gripping the left handle of the controller */
+    SDL_GAMEPAD_CAPSENSE_RIGHT_GRIP,    /**< Activated by gripping the right handle of the controller */
+    SDL_GAMEPAD_CAPSENSE_COUNT
+} SDL_GamepadCapSenseType;
+
 /**
 /**
  * Types of gamepad control bindings.
  * Types of gamepad control bindings.
  *
  *
@@ -1510,6 +1528,36 @@ extern SDL_DECLSPEC float SDLCALL SDL_GetGamepadSensorDataRate(SDL_Gamepad *game
  */
  */
 extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values);
 extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *data, int num_values);
 
 
+/**
+ * Return whether a gamepad has a particular capsense.
+ *
+ * \param gamepad the gamepad to query.
+ * \param type the type of capsense to query.
+ * \returns true if the capsense exists, false otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_GetGamepadCapSense
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GamepadHasCapSense(SDL_Gamepad *gamepad, SDL_GamepadCapSenseType type);
+
+/**
+ * Get the current state of a capsense on a gamepad.
+ *
+ * \param gamepad a gamepad.
+ * \param type the type of capsense to query.
+ * \returns true if the capsense is touched, false otherwise.
+ *
+ * \threadsafety It is safe to call this function from any thread.
+ *
+ * \since This function is available since SDL 3.6.0.
+ *
+ * \sa SDL_GamepadHasCapSense
+ */
+extern SDL_DECLSPEC bool SDLCALL SDL_GetGamepadCapSense(SDL_Gamepad *gamepad, SDL_GamepadCapSenseType type);
+
 /**
 /**
  * Start a rumble effect on a gamepad.
  * Start a rumble effect on a gamepad.
  *
  *

+ 2 - 0
src/dynapi/SDL_dynapi.exports

@@ -1288,3 +1288,5 @@ _SDL_IsPhone
 _SDL_LoadJPG_IO
 _SDL_LoadJPG_IO
 _SDL_LoadJPG
 _SDL_LoadJPG
 _SDL_HasSVE2
 _SDL_HasSVE2
+_SDL_GamepadHasCapSense
+_SDL_GetGamepadCapSense

+ 2 - 0
src/dynapi/SDL_dynapi.sym

@@ -1289,6 +1289,8 @@ SDL3_0.0.0 {
     SDL_LoadJPG_IO;
     SDL_LoadJPG_IO;
     SDL_LoadJPG;
     SDL_LoadJPG;
     SDL_HasSVE2;
     SDL_HasSVE2;
+    SDL_GamepadHasCapSense;
+    SDL_GetGamepadCapSense;
     # extra symbols go here (don't modify this line)
     # extra symbols go here (don't modify this line)
   local: *;
   local: *;
 };
 };

+ 2 - 0
src/dynapi/SDL_dynapi_overrides.h

@@ -1315,3 +1315,5 @@
 #define SDL_LoadJPG_IO SDL_LoadJPG_IO_REAL
 #define SDL_LoadJPG_IO SDL_LoadJPG_IO_REAL
 #define SDL_LoadJPG SDL_LoadJPG_REAL
 #define SDL_LoadJPG SDL_LoadJPG_REAL
 #define SDL_HasSVE2 SDL_HasSVE2_REAL
 #define SDL_HasSVE2 SDL_HasSVE2_REAL
+#define SDL_GamepadHasCapSense SDL_GamepadHasCapSense_REAL
+#define SDL_GetGamepadCapSense SDL_GetGamepadCapSense_REAL

+ 2 - 0
src/dynapi/SDL_dynapi_procs.h

@@ -1323,3 +1323,5 @@ SDL_DYNAPI_PROC(bool,SDL_IsPhone,(void),(),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadJPG_IO,(SDL_IOStream *a,bool b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadJPG_IO,(SDL_IOStream *a,bool b),(a,b),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadJPG,(const char *a),(a),return)
 SDL_DYNAPI_PROC(SDL_Surface*,SDL_LoadJPG,(const char *a),(a),return)
 SDL_DYNAPI_PROC(bool,SDL_HasSVE2,(void),(),return)
 SDL_DYNAPI_PROC(bool,SDL_HasSVE2,(void),(),return)
+SDL_DYNAPI_PROC(bool,SDL_GamepadHasCapSense,(SDL_Gamepad *a,SDL_GamepadCapSenseType b),(a,b),return)
+SDL_DYNAPI_PROC(bool,SDL_GetGamepadCapSense,(SDL_Gamepad *a,SDL_GamepadCapSenseType b),(a,b),return)

+ 4 - 0
src/events/SDL_categories.c

@@ -134,6 +134,10 @@ SDL_EventCategory SDL_GetEventCategory(Uint32 type)
     case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
     case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
         return SDL_EVENTCATEGORY_GSENSOR;
         return SDL_EVENTCATEGORY_GSENSOR;
 
 
+    case SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH:
+    case SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE:
+        return SDL_EVENTCATEGORY_GCAPSENSE;
+
     case SDL_EVENT_FINGER_DOWN:
     case SDL_EVENT_FINGER_DOWN:
     case SDL_EVENT_FINGER_UP:
     case SDL_EVENT_FINGER_UP:
     case SDL_EVENT_FINGER_CANCELED:
     case SDL_EVENT_FINGER_CANCELED:

+ 1 - 0
src/events/SDL_categories_c.h

@@ -49,6 +49,7 @@ typedef enum SDL_EventCategory
     SDL_EVENTCATEGORY_GBUTTON,
     SDL_EVENTCATEGORY_GBUTTON,
     SDL_EVENTCATEGORY_GTOUCHPAD,
     SDL_EVENTCATEGORY_GTOUCHPAD,
     SDL_EVENTCATEGORY_GSENSOR,
     SDL_EVENTCATEGORY_GSENSOR,
+    SDL_EVENTCATEGORY_GCAPSENSE,
     SDL_EVENTCATEGORY_ADEVICE,
     SDL_EVENTCATEGORY_ADEVICE,
     SDL_EVENTCATEGORY_CDEVICE,
     SDL_EVENTCATEGORY_CDEVICE,
     SDL_EVENTCATEGORY_SENSOR,
     SDL_EVENTCATEGORY_SENSOR,

+ 12 - 0
src/events/SDL_events.c

@@ -758,6 +758,18 @@ int SDL_GetEventDescription(const SDL_Event *event, char *buf, int buflen)
                            event->gsensor.data[0], event->gsensor.data[1], event->gsensor.data[2]);
                            event->gsensor.data[0], event->gsensor.data[1], event->gsensor.data[2]);
         break;
         break;
 
 
+#define PRINT_CAPSENSE_EVENT(event)                                                                            \
+    (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " which=%d capsense=%u state=%s)", \
+                       event->gcapsense.timestamp, (int)event->gcapsense.which,                                \
+                       event->gcapsense.capsense, event->gcapsense.down ? "touch" : "release")
+        SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH)
+        PRINT_CAPSENSE_EVENT(event);
+        break;
+        SDL_EVENT_CASE(SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE)
+        PRINT_CAPSENSE_EVENT(event);
+        break;
+#undef PRINT_CAPSENSE_EVENT
+
 #define PRINT_FINGER_EVENT(event)                                                                                                                      \
 #define PRINT_FINGER_EVENT(event)                                                                                                                      \
     (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " touchid=%" SDL_PRIu64 " fingerid=%" SDL_PRIu64 " x=%f y=%f dx=%f dy=%f pressure=%f)", \
     (void)SDL_snprintf(details, sizeof(details), " (timestamp=%" SDL_PRIu64 " touchid=%" SDL_PRIu64 " fingerid=%" SDL_PRIu64 " x=%f y=%f dx=%f dy=%f pressure=%f)", \
                        event->tfinger.timestamp, event->tfinger.touchID,                                                              \
                        event->tfinger.timestamp, event->tfinger.touchID,                                                              \

+ 47 - 3
src/joystick/SDL_gamepad.c

@@ -1254,10 +1254,10 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
             SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,", sizeof(mapping_string));
             SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,", sizeof(mapping_string));
         } else if (SDL_IsJoystickSteamDeck(vendor, product)) {
         } else if (SDL_IsJoystickSteamDeck(vendor, product)) {
             // The Steam Deck's built-in controller has QAM, 4 back buttons, L/R trackpads, and L/R capacitive touch sticks
             // The Steam Deck's built-in controller has QAM, 4 back buttons, L/R trackpads, and L/R capacitive touch sticks
-            SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,misc3:b19,misc4:b18", sizeof(mapping_string));
+            SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16", sizeof(mapping_string));
         } else if (SDL_IsJoystickSteamTriton(vendor, product)) {
         } else if (SDL_IsJoystickSteamTriton(vendor, product)) {
             // Second generation Steam controllers have 4 back paddle buttons
             // Second generation Steam controllers have 4 back paddle buttons
-            SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,misc3:b19,misc4:b18,misc5:b21,misc6:b20", sizeof(mapping_string));
+            SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16", sizeof(mapping_string));
         } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) ||
         } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) ||
                    SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
                    SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
             // Nintendo Switch Pro controllers have a screenshot button
             // Nintendo Switch Pro controllers have a screenshot button
@@ -1281,7 +1281,7 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
             }
             }
         } else if (SDL_IsJoystickHoriSteamController(vendor, product)) {
         } else if (SDL_IsJoystickHoriSteamController(vendor, product)) {
             /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */
             /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */
-            SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc1:b11,misc3:b16,misc4:b17", sizeof(mapping_string));
+            SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc1:b11", sizeof(mapping_string));
         } else if (SDL_IsJoystickFlydigiController(vendor, product)) {
         } else if (SDL_IsJoystickFlydigiController(vendor, product)) {
             SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string));
             SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string));
             if (guid.data[15] >= SDL_FLYDIGI_VADER2) {
             if (guid.data[15] >= SDL_FLYDIGI_VADER2) {
@@ -3952,6 +3952,48 @@ bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *
     return SDL_Unsupported();
     return SDL_Unsupported();
 }
 }
 
 
+bool SDL_GamepadHasCapSense(SDL_Gamepad *gamepad, SDL_GamepadCapSenseType type)
+{
+    bool result = false;
+
+    SDL_LockJoysticks();
+    {
+        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
+        if (joystick) {
+            for (int i = 0; i < joystick->ncapsenses; ++i) {
+                if (joystick->capsenses[i].type == type) {
+                    result = true;
+                    break;
+                }
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
+bool SDL_GetGamepadCapSense(SDL_Gamepad *gamepad, SDL_GamepadCapSenseType type)
+{
+    bool result = false;
+
+    SDL_LockJoysticks();
+    {
+        SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
+        if (joystick) {
+            for (int i = 0; i < joystick->ncapsenses; ++i) {
+                if (joystick->capsenses[i].type == type) {
+                    result = joystick->capsenses[i].down;
+                    break;
+                }
+            }
+        }
+    }
+    SDL_UnlockJoysticks();
+
+    return result;
+}
+
 SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad)
 SDL_JoystickID SDL_GetGamepadID(SDL_Gamepad *gamepad)
 {
 {
     SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
     SDL_Joystick *joystick = SDL_GetGamepadJoystick(gamepad);
@@ -4470,6 +4512,8 @@ static const Uint32 SDL_gamepad_event_list[] = {
     SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION,
     SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION,
     SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
     SDL_EVENT_GAMEPAD_TOUCHPAD_UP,
     SDL_EVENT_GAMEPAD_SENSOR_UPDATE,
     SDL_EVENT_GAMEPAD_SENSOR_UPDATE,
+    SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH,
+    SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE,
 };
 };
 
 
 void SDL_SetGamepadEventsEnabled(bool enabled)
 void SDL_SetGamepadEventsEnabled(bool enabled)

+ 1 - 1
src/joystick/SDL_gamepad_db.h

@@ -723,7 +723,7 @@ static const char *s_GamepadMappings[] = {
     "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
     "05000000de2800000212000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
     "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
     "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
     "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
     "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,dpdown:b14,dpleft:b15,dpright:b13,dpup:b12,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,paddle1:b11,paddle2:b10,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
-    "03000000de2800000512000000016800,Steam Deck Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,misc3:b19,misc4:b18,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
+    "03000000de2800000512000000016800,Steam Deck Controller,a:b0,b:b1,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,touchpad:b17,misc2:b16,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,",
     "03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,",
     "03000000de2800000512000011010000,Steam Deck,a:b3,b:b4,back:b11,dpdown:b17,dpleft:b18,dpright:b19,dpup:b16,guide:b13,leftshoulder:b7,leftstick:b14,lefttrigger:a9,leftx:a0,lefty:a1,misc1:b2,paddle1:b21,paddle2:b20,paddle3:b23,paddle4:b22,rightshoulder:b8,rightstick:b15,righttrigger:a8,rightx:a2,righty:a3,start:b12,x:b5,y:b6,",
     "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
     "03000000de280000ff11000001000000,Steam Virtual Gamepad,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
     "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",
     "0500000011010000311400001b010000,SteelSeries Stratus Duo,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b32,leftshoulder:b6,leftstick:b13,lefttrigger:a5,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a4,rightx:a2,righty:a3,start:b11,x:b3,y:b4,",

+ 67 - 0
src/joystick/SDL_joystick.c

@@ -2441,6 +2441,26 @@ void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type,
     }
     }
 }
 }
 
 
+void SDL_PrivateJoystickAddCapSense(SDL_Joystick *joystick, SDL_GamepadCapSenseType type)
+{
+    int ncapsenses;
+    SDL_JoystickCapSenseInfo *capsenses;
+
+    SDL_AssertJoysticksLocked();
+
+    ncapsenses = joystick->ncapsenses + 1;
+    capsenses = (SDL_JoystickCapSenseInfo *)SDL_realloc(joystick->capsenses, (ncapsenses * sizeof(SDL_JoystickCapSenseInfo)));
+    if (capsenses) {
+        SDL_JoystickCapSenseInfo *capsense = &capsenses[ncapsenses - 1];
+
+        capsense->type = type;
+        capsense->down = false;
+
+        joystick->ncapsenses = ncapsenses;
+        joystick->capsenses = capsenses;
+    }
+}
+
 void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id)
 void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id)
 {
 {
     SDL_JoystickDriver *driver;
     SDL_JoystickDriver *driver;
@@ -3952,6 +3972,53 @@ void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_Sensor
     }
     }
 }
 }
 
 
+void SDL_SendJoystickCapSense(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadCapSenseType type, bool down)
+{
+    SDL_AssertJoysticksLocked();
+
+    // We ignore events if we don't have keyboard focus, except for button
+    // (capsense) release
+    if (SDL_PrivateJoystickShouldIgnoreEvent()) {
+        if (down) {
+            return;
+        }
+    }
+
+    for (int i = 0; i < joystick->ncapsenses; ++i) {
+        SDL_JoystickCapSenseInfo *capsense = &joystick->capsenses[i];
+
+        if (capsense->type == type) {
+            SDL_Event event;
+
+            // Ignore duplicate events
+            if (down == capsense->down) {
+                return;
+            }
+
+            // Update internal joystick state
+            capsense->down = down;
+            joystick->update_complete = timestamp;
+
+            if (down) {
+                event.type = SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH;
+            } else {
+                event.type = SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE;
+            }
+
+            // Post the event, if desired
+            if (SDL_EventEnabled(event.type)) {
+                event.common.timestamp = timestamp;
+                event.gcapsense.which = joystick->instance_id;
+                event.gcapsense.capsense = type;
+                event.gcapsense.down = down;
+                SDL_PushEvent(&event);
+            }
+
+            break;
+        }
+    }
+}
+
 static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int *max_entries, Uint32 **entries)
 static void SDL_LoadVIDPIDListFromHint(const char *hint, int *num_entries, int *max_entries, Uint32 **entries)
 {
 {
     Uint32 entry;
     Uint32 entry;

+ 2 - 0
src/joystick/SDL_joystick_c.h

@@ -181,6 +181,7 @@ extern bool SDL_ShouldIgnoreJoystick(Uint16 vendor_id, Uint16 product_id, Uint16
 extern void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers);
 extern void SDL_PrivateJoystickAddTouchpad(SDL_Joystick *joystick, int nfingers);
 extern void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate);
 extern void SDL_PrivateJoystickAddSensor(SDL_Joystick *joystick, SDL_SensorType type, float rate);
 extern void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type, float rate);
 extern void SDL_PrivateJoystickSensorRate(SDL_Joystick *joystick, SDL_SensorType type, float rate);
+extern void SDL_PrivateJoystickAddCapSense(SDL_Joystick *joystick, SDL_GamepadCapSenseType type);
 extern void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id);
 extern void SDL_PrivateJoystickAdded(SDL_JoystickID instance_id);
 extern bool SDL_IsJoystickBeingAdded(void);
 extern bool SDL_IsJoystickBeingAdded(void);
 extern void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id);
 extern void SDL_PrivateJoystickRemoved(SDL_JoystickID instance_id);
@@ -191,6 +192,7 @@ extern void SDL_SendJoystickHat(Uint64 timestamp, SDL_Joystick *joystick, Uint8
 extern void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 button, bool down);
 extern void SDL_SendJoystickButton(Uint64 timestamp, SDL_Joystick *joystick, Uint8 button, bool down);
 extern void SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure);
 extern void SDL_SendJoystickTouchpad(Uint64 timestamp, SDL_Joystick *joystick, int touchpad, int finger, bool down, float x, float y, float pressure);
 extern void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values);
 extern void SDL_SendJoystickSensor(Uint64 timestamp, SDL_Joystick *joystick, SDL_SensorType type, Uint64 sensor_timestamp, const float *data, int num_values);
+extern void SDL_SendJoystickCapSense(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadCapSenseType type, bool down);
 extern void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent);
 extern void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent);
 
 
 // Function to get the Steam virtual gamepad info for a joystick
 // Function to get the Steam virtual gamepad info for a joystick

+ 9 - 0
src/joystick/SDL_sysjoystick.h

@@ -72,6 +72,12 @@ typedef struct SDL_JoystickSensorInfo
     float data[3]; // If this needs to expand, update SDL_GamepadSensorEvent
     float data[3]; // If this needs to expand, update SDL_GamepadSensorEvent
 } SDL_JoystickSensorInfo;
 } SDL_JoystickSensorInfo;
 
 
+typedef struct SDL_JoystickCapSenseInfo
+{
+    SDL_GamepadCapSenseType type;
+    bool down;
+} SDL_JoystickCapSenseInfo;
+
 #define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
 #define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
 
 
 struct SDL_Joystick
 struct SDL_Joystick
@@ -105,6 +111,9 @@ struct SDL_Joystick
     int nsensors_enabled _guarded;
     int nsensors_enabled _guarded;
     SDL_JoystickSensorInfo *sensors _guarded;
     SDL_JoystickSensorInfo *sensors _guarded;
 
 
+    int ncapsenses _guarded;                      // Number of capsense sources on the joystick
+    SDL_JoystickCapSenseInfo *capsenses _guarded; // Current capsense states
+
     Uint16 low_frequency_rumble _guarded;
     Uint16 low_frequency_rumble _guarded;
     Uint16 high_frequency_rumble _guarded;
     Uint16 high_frequency_rumble _guarded;
     Uint64 rumble_expiration _guarded;
     Uint64 rumble_expiration _guarded;

+ 6 - 4
src/joystick/hidapi/SDL_hidapi_steam_hori.c

@@ -39,8 +39,6 @@ enum
     SDL_GAMEPAD_BUTTON_HORI_FL,
     SDL_GAMEPAD_BUTTON_HORI_FL,
     SDL_GAMEPAD_BUTTON_HORI_M1,
     SDL_GAMEPAD_BUTTON_HORI_M1,
     SDL_GAMEPAD_BUTTON_HORI_M2,
     SDL_GAMEPAD_BUTTON_HORI_M2,
-    SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L,
-    SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R,
     SDL_GAMEPAD_NUM_HORI_BUTTONS
     SDL_GAMEPAD_NUM_HORI_BUTTONS
 };
 };
 
 
@@ -133,6 +131,10 @@ static bool HIDAPI_DriverSteamHori_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J
 
 
     const Uint64 sensorupdatestep_ms = ctx->wireless ? 8333 : 4000; // Equivalent to 120hz / 250hz respectively
     const Uint64 sensorupdatestep_ms = ctx->wireless ? 8333 : 4000; // Equivalent to 120hz / 250hz respectively
     ctx->simulated_sensor_step_ns = SDL_US_TO_NS(sensorupdatestep_ms);
     ctx->simulated_sensor_step_ns = SDL_US_TO_NS(sensorupdatestep_ms);
+
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK);
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK);
+
     return true;
     return true;
 }
 }
 
 
@@ -273,8 +275,8 @@ static void HIDAPI_DriverSteamHori_HandleStatePacket(SDL_Joystick *joystick, SDL
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[7] & 0x02) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[7] & 0x04) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_M2, ((data[7] & 0x08) != 0));
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L, ((data[7] & 0x10) != 0));
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R, ((data[7] & 0x20) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK, ((data[7] & 0x10) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK, ((data[7] & 0x20) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FR, ((data[7] & 0x40) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_HORI_FL, ((data[7] & 0x80) != 0));
     }
     }

+ 13 - 12
src/joystick/hidapi/SDL_hidapi_steam_triton.c

@@ -47,10 +47,6 @@ enum
     SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE2,
     SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE2,
     SDL_GAMEPAD_BUTTON_TRITON_RIGHT_TOUCHPAD,
     SDL_GAMEPAD_BUTTON_TRITON_RIGHT_TOUCHPAD,
     SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD,
     SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD,
-    SDL_GAMEPAD_BUTTON_TRITON_RIGHT_JOYSTICK_TOUCH,
-    SDL_GAMEPAD_BUTTON_TRITON_LEFT_JOYSTICK_TOUCH,
-    SDL_GAMEPAD_BUTTON_TRITON_RIGHT_GRIP_TOUCH,
-    SDL_GAMEPAD_BUTTON_TRITON_LEFT_GRIP_TOUCH,
     SDL_GAMEPAD_NUM_TRITON_BUTTONS,
     SDL_GAMEPAD_NUM_TRITON_BUTTONS,
 };
 };
 
 
@@ -187,15 +183,15 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD,
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD,
                                ((pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_CLICK) != 0));
                                ((pTritonReport->buttons & TRITON_LEFT_TOUCHPAD_CLICK) != 0));
 
 
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_JOYSTICK_TOUCH,
-                               ((pTritonReport->buttons & TRITON_RIGHT_JOYSTICK_TOUCH) != 0));
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_JOYSTICK_TOUCH,
-                               ((pTritonReport->buttons & TRITON_LEFT_JOYSTICK_TOUCH) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK,
+                                 ((pTritonReport->buttons & TRITON_RIGHT_JOYSTICK_TOUCH) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK,
+                                 ((pTritonReport->buttons & TRITON_LEFT_JOYSTICK_TOUCH) != 0));
 
 
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_RIGHT_GRIP_TOUCH,
-                               ((pTritonReport->buttons & TRITON_RIGHT_GRIP_TOUCH) != 0));
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_GRIP_TOUCH,
-                               ((pTritonReport->buttons & TRITON_LEFT_GRIP_TOUCH) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_GRIP,
+                                 ((pTritonReport->buttons & TRITON_RIGHT_GRIP_TOUCH) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_LEFT_GRIP,
+                                 ((pTritonReport->buttons & TRITON_LEFT_GRIP_TOUCH) != 0));
 
 
         if (pTritonReport->buttons & TRITON_LBUTTON_DPAD_UP) {
         if (pTritonReport->buttons & TRITON_LBUTTON_DPAD_UP) {
             hat |= SDL_HAT_UP;
             hat |= SDL_HAT_UP;
@@ -480,6 +476,11 @@ static bool HIDAPI_DriverSteamTriton_OpenJoystick(SDL_HIDAPI_Device *device, SDL
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
 
 
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK);
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK);
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_LEFT_GRIP);
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_GRIP);
+
     return true;
     return true;
 }
 }
 
 

+ 7 - 6
src/joystick/hidapi/SDL_hidapi_steamdeck.c

@@ -41,8 +41,6 @@ enum
     SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
     SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
     SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_TOUCHPAD,
     SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_TOUCHPAD,
     SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD,
     SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD,
-    SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_JOYSTICK_TOUCH,
-    SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_JOYSTICK_TOUCH,
     SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS,
     SDL_GAMEPAD_NUM_STEAM_DECK_BUTTONS,
 };
 };
 
 
@@ -202,10 +200,10 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD,
         SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD,
                                ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_LEFT_PAD) != 0));
                                ((pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_LEFT_PAD) != 0));
 
 
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_JOYSTICK_TOUCH,
-                               ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_RSTICK_TOUCH) != 0));
-        SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_JOYSTICK_TOUCH,
-                               ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_LSTICK_TOUCH) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK,
+                                 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_RSTICK_TOUCH) != 0));
+        SDL_SendJoystickCapSense(timestamp, joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK,
+                                 ((pInReport->payload.deckState.ulButtonsH & STEAMDECK_HBUTTON_LSTICK_TOUCH) != 0));
 
 
         if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) {
         if (pInReport->payload.deckState.ulButtonsL & STEAMDECK_LBUTTON_DPAD_UP) {
             hat |= SDL_HAT_UP;
             hat |= SDL_HAT_UP;
@@ -397,6 +395,9 @@ static bool HIDAPI_DriverSteamDeck_OpenJoystick(SDL_HIDAPI_Device *device, SDL_J
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
     SDL_PrivateJoystickAddTouchpad(joystick, 1);
 
 
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK);
+    SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK);
+
     return true;
     return true;
 }
 }
 
 

+ 27 - 0
src/test/SDL_test_common.c

@@ -1582,6 +1582,23 @@ static const char *GamepadButtonName(const SDL_GamepadButton button)
     }
     }
 }
 }
 
 
+static const char *CapSenseName(const SDL_GamepadCapSenseType type)
+{
+    switch (type) {
+#define CAPSENSE_CASE(cap)           \
+    case SDL_GAMEPAD_CAPSENSE_##cap: \
+        return #cap
+        CAPSENSE_CASE(INVALID);
+        CAPSENSE_CASE(LEFT_STICK);
+        CAPSENSE_CASE(RIGHT_STICK);
+        CAPSENSE_CASE(LEFT_GRIP);
+        CAPSENSE_CASE(RIGHT_GRIP);
+#undef CAPSENSE_CASE
+    default:
+        return "???";
+    }
+}
+
 void SDLTest_PrintEvent(const SDL_Event *event)
 void SDLTest_PrintEvent(const SDL_Event *event)
 {
 {
     switch (event->type) {
     switch (event->type) {
@@ -1878,6 +1895,16 @@ void SDLTest_PrintEvent(const SDL_Event *event)
                 event->gbutton.which, event->gbutton.button,
                 event->gbutton.which, event->gbutton.button,
                 GamepadButtonName((SDL_GamepadButton)event->gbutton.button));
                 GamepadButtonName((SDL_GamepadButton)event->gbutton.button));
         break;
         break;
+    case SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH:
+        SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 "capsense %u ('%s') touch",
+                event->gcapsense.which, event->gcapsense.capsense,
+                CapSenseName((SDL_GamepadCapSenseType)event->gcapsense.capsense));
+        break;
+    case SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE:
+        SDL_Log("SDL EVENT: Gamepad %" SDL_PRIu32 " capsense %u ('%s') release",
+                event->gcapsense.which, event->gcapsense.capsense,
+                CapSenseName((SDL_GamepadCapSenseType)event->gcapsense.capsense));
+        break;
     case SDL_EVENT_CLIPBOARD_UPDATE:
     case SDL_EVENT_CLIPBOARD_UPDATE:
         SDL_Log("SDL EVENT: Clipboard updated");
         SDL_Log("SDL EVENT: Clipboard updated");
         break;
         break;

+ 2 - 1
test/CMakeLists.txt

@@ -102,7 +102,7 @@ if(NOT CMAKE_VERSION VERSION_LESS 3.20)
 endif()
 endif()
 
 
 if(DOS)
 if(DOS)
-    set(NAME83_LONG "unifont-15.1.05.hex;unifont-15.1.05-license.txt;physaudiodev.png;logaudiodev.png;audiofile.png;soundboard.png;soundboard_levels.png;trashcan.png;msdf_font.png;msdf_font.csv;gamepad_front.png;gamepad_back.png;gamepad_face_abxy.png;gamepad_face_axby.png;gamepad_face_bayx.png;gamepad_face_sony.png;gamepad_battery.png;gamepad_battery_unknown.png;gamepad_battery_wired.png;gamepad_touchpad.png;gamepad_dual_touchpad.png;gamepad_button.png;gamepad_button_small.png;gamepad_button_background.png;gamepad_axis.png;gamepad_axis_arrow.png;gamepad_wired.png;gamepad_wireless.png;sdl-test_round.png")
+    set(NAME83_LONG "unifont-15.1.05.hex;unifont-15.1.05-license.txt;physaudiodev.png;logaudiodev.png;audiofile.png;soundboard.png;soundboard_levels.png;trashcan.png;msdf_font.png;msdf_font.csv;gamepad_front.png;gamepad_back.png;gamepad_face_abxy.png;gamepad_face_axby.png;gamepad_face_bayx.png;gamepad_face_sony.png;gamepad_battery.png;gamepad_battery_unknown.png;gamepad_battery_wired.png;gamepad_touchpad.png;gamepad_dual_touchpad.png;gamepad_button.png;gamepad_button_small.png;gamepad_button_background.png;gamepad_axis.png;gamepad_axis_arrow.png;gamepad_wired.png;gamepad_wireless.png;gamepad_grip_sense.png;sdl-test_round.png")
     set(DOS83_SHORT "UNIFONT.HEX;UNIFONTL.TXT;PHYSADEV.PNG;LOGADEV.PNG;AUDIOFIL.PNG;SNDBRD.PNG;SNDLVL.PNG;TRASHCAN.PNG;MSDFFONT.PNG;MSDFFONT.CSV;GP_FRONT.PNG;GP_BACK.PNG;GP_FABXY.PNG;GP_FAXBY.PNG;GP_FBAYX.PNG;GP_FSONY.PNG;GP_BATT.PNG;GP_BATTX.PNG;GP_BATTW.PNG;GP_TOUCH.PNG;GP_BTN.PNG;GP_BTNSM.PNG;GP_BTNBG.PNG;GP_AXIS.PNG;GP_AXARW.PNG;GP_WIRED.PNG;GP_WLESS.PNG;SDLROUND.PNG")
     set(DOS83_SHORT "UNIFONT.HEX;UNIFONTL.TXT;PHYSADEV.PNG;LOGADEV.PNG;AUDIOFIL.PNG;SNDBRD.PNG;SNDLVL.PNG;TRASHCAN.PNG;MSDFFONT.PNG;MSDFFONT.CSV;GP_FRONT.PNG;GP_BACK.PNG;GP_FABXY.PNG;GP_FAXBY.PNG;GP_FBAYX.PNG;GP_FSONY.PNG;GP_BATT.PNG;GP_BATTX.PNG;GP_BATTW.PNG;GP_TOUCH.PNG;GP_BTN.PNG;GP_BTNSM.PNG;GP_BTNBG.PNG;GP_AXIS.PNG;GP_AXARW.PNG;GP_WIRED.PNG;GP_WLESS.PNG;SDLROUND.PNG")
 endif()
 endif()
 
 
@@ -315,6 +315,7 @@ files2headers(gamepad_image_headers
     gamepad_face_bayx.png
     gamepad_face_bayx.png
     gamepad_face_sony.png
     gamepad_face_sony.png
     gamepad_front.png
     gamepad_front.png
+    gamepad_grip_sense.png
     gamepad_touchpad.png
     gamepad_touchpad.png
     gamepad_wired.png
     gamepad_wired.png
     gamepad_wireless.png
     gamepad_wireless.png

+ 73 - 0
test/gamepad_grip_sense.h

@@ -0,0 +1,73 @@
+unsigned char gamepad_grip_sense_png[] = {
+  0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
+  0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x14, 0x00, 0x00, 0x00, 0x3c,
+  0x08, 0x06, 0x00, 0x00, 0x00, 0x66, 0x76, 0x9a, 0x56, 0x00, 0x00, 0x00,
+  0x19, 0x74, 0x45, 0x58, 0x74, 0x53, 0x6f, 0x66, 0x74, 0x77, 0x61, 0x72,
+  0x65, 0x00, 0x41, 0x64, 0x6f, 0x62, 0x65, 0x20, 0x49, 0x6d, 0x61, 0x67,
+  0x65, 0x52, 0x65, 0x61, 0x64, 0x79, 0x71, 0xc9, 0x65, 0x3c, 0x00, 0x00,
+  0x02, 0xe7, 0x49, 0x44, 0x41, 0x54, 0x78, 0xda, 0x94, 0x58, 0xcb, 0xb1,
+  0xea, 0x30, 0x0c, 0x75, 0x8c, 0x0b, 0xa0, 0x24, 0x06, 0x8a, 0xa0, 0x04,
+  0xd6, 0x54, 0xc2, 0x16, 0xe8, 0x20, 0x1b, 0x86, 0x02, 0x20, 0x30, 0x43,
+  0x41, 0xc0, 0x9a, 0x4f, 0x2e, 0xf2, 0x7b, 0xca, 0x55, 0x94, 0x23, 0xe1,
+  0xeb, 0x19, 0x26, 0x21, 0xb1, 0x64, 0x49, 0xe7, 0x48, 0x72, 0x5c, 0xd5,
+  0x75, 0x1d, 0xe4, 0xd8, 0x6e, 0xb7, 0xed, 0xf1, 0x78, 0x0c, 0x25, 0x63,
+  0x36, 0x9b, 0x85, 0xc3, 0xe1, 0x50, 0xc9, 0x67, 0x89, 0x6f, 0xd6, 0xeb,
+  0x75, 0x7b, 0x3a, 0x9d, 0xf2, 0xfd, 0x6a, 0xb5, 0x0a, 0xa3, 0xd1, 0x28,
+  0xc4, 0x18, 0x4d, 0x65, 0x8f, 0xc7, 0x23, 0x2c, 0x97, 0xcb, 0x50, 0x55,
+  0x55, 0x3b, 0x9d, 0x4e, 0xc3, 0xc7, 0x88, 0xac, 0xb8, 0x22, 0x0b, 0x37,
+  0x9b, 0x4d, 0xdb, 0x34, 0x4d, 0x56, 0x94, 0x52, 0xa2, 0x49, 0x9d, 0x60,
+  0xdb, 0xb6, 0xf9, 0xa7, 0x9f, 0xd1, 0x62, 0x74, 0x7d, 0x3e, 0x9f, 0x59,
+  0x31, 0x5b, 0x9b, 0x4d, 0x60, 0x65, 0x64, 0x95, 0x54, 0xc2, 0xf7, 0x52,
+  0x59, 0xb6, 0xe2, 0xf3, 0x9f, 0x9f, 0x93, 0x0c, 0xc9, 0x72, 0x98, 0xaa,
+  0x8f, 0xb9, 0xed, 0x78, 0x3c, 0x0e, 0x93, 0xc9, 0xa4, 0x13, 0x64, 0x01,
+  0xad, 0x40, 0x2a, 0xa7, 0xeb, 0xfb, 0xfd, 0xee, 0xae, 0xe7, 0xf3, 0x39,
+  0xdc, 0xef, 0xf7, 0x40, 0x6f, 0x5b, 0x69, 0x1d, 0xb9, 0x42, 0x13, 0xf8,
+  0xca, 0x83, 0x5d, 0xb4, 0xac, 0x66, 0xd7, 0x93, 0xb4, 0x42, 0xba, 0x82,
+  0xe2, 0x86, 0xe6, 0xea, 0xd1, 0x83, 0x51, 0x4f, 0xd0, 0x21, 0x90, 0xb1,
+  0x45, 0xf3, 0x07, 0x0a, 0xb5, 0x22, 0x1d, 0x4f, 0x72, 0x9b, 0xdf, 0x31,
+  0xd2, 0x7a, 0x7e, 0x44, 0xab, 0x49, 0xb7, 0xf9, 0xb9, 0x0e, 0x87, 0x66,
+  0xc3, 0x40, 0x21, 0xc7, 0x45, 0x2f, 0x80, 0xe2, 0x29, 0x95, 0x6a, 0x70,
+  0xa2, 0x14, 0x46, 0x28, 0x4a, 0x12, 0xcb, 0x77, 0xda, 0x83, 0x5e, 0xea,
+  0x79, 0xe8, 0x5a, 0x68, 0x5a, 0x0c, 0x88, 0xa5, 0x80, 0x94, 0x8e, 0x58,
+  0xa2, 0x48, 0xff, 0xf7, 0x16, 0x89, 0x88, 0xac, 0x5a, 0x40, 0x52, 0xc4,
+  0x22, 0xfa, 0x00, 0x14, 0x99, 0xab, 0x9a, 0x1a, 0x12, 0x20, 0xc4, 0x00,
+  0xa9, 0x3c, 0x21, 0xe8, 0x39, 0xe9, 0x35, 0xef, 0x64, 0xe6, 0x58, 0x39,
+  0x0d, 0x63, 0x58, 0x12, 0x3b, 0x97, 0x87, 0x9e, 0xa2, 0x5e, 0x7c, 0x44,
+  0x1c, 0x91, 0x01, 0x03, 0x85, 0x56, 0xac, 0x3c, 0x20, 0xb4, 0xdb, 0x83,
+  0x5c, 0x46, 0x88, 0xa2, 0x85, 0xac, 0x45, 0x93, 0xb6, 0x80, 0x2b, 0xb0,
+  0x17, 0x0a, 0x04, 0x0e, 0x54, 0x88, 0x5c, 0x40, 0x55, 0xc8, 0x23, 0x37,
+  0x4c, 0x3d, 0x9d, 0x35, 0x50, 0xf0, 0x7f, 0x58, 0x60, 0xf9, 0xd2, 0x24,
+  0x46, 0xad, 0xd3, 0x8a, 0x27, 0xa4, 0x4d, 0x09, 0x07, 0x65, 0x03, 0x2b,
+  0x72, 0x19, 0x35, 0x1e, 0xbd, 0x10, 0x02, 0xc4, 0xb4, 0x50, 0xe6, 0xf1,
+  0xb7, 0xe1, 0x55, 0x9e, 0x64, 0xf5, 0x86, 0x12, 0x62, 0xa3, 0x38, 0x26,
+  0x24, 0x60, 0x21, 0x68, 0xd1, 0x89, 0xe2, 0xda, 0xc9, 0x6a, 0xaa, 0x68,
+  0x94, 0x4b, 0x38, 0x29, 0xe7, 0x45, 0x44, 0x01, 0xa4, 0x04, 0x29, 0xe0,
+  0x32, 0x07, 0x73, 0x59, 0xc6, 0x84, 0x27, 0x7a, 0x80, 0x58, 0x00, 0x26,
+  0xe4, 0x82, 0x6c, 0x9b, 0x88, 0x36, 0x1e, 0xca, 0x70, 0x6f, 0x83, 0xc0,
+  0xb0, 0x2c, 0xd6, 0xcf, 0x93, 0x95, 0xf8, 0xf2, 0xde, 0x4a, 0x33, 0x94,
+  0x04, 0xd1, 0x2a, 0x98, 0x12, 0x65, 0xf9, 0xeb, 0x35, 0x75, 0x51, 0x37,
+  0x7b, 0xb4, 0x29, 0x71, 0x0d, 0x29, 0x41, 0x79, 0x9d, 0x4a, 0x33, 0xc3,
+  0xab, 0x30, 0x66, 0xa6, 0x78, 0xd5, 0x59, 0x7f, 0x15, 0x68, 0xaf, 0x78,
+  0x5e, 0xf2, 0xaa, 0x09, 0x2a, 0x61, 0x52, 0x18, 0xed, 0x23, 0xa3, 0x45,
+  0x5c, 0xab, 0x60, 0xf0, 0x3b, 0xab, 0x2e, 0x42, 0x50, 0xac, 0x6d, 0x5d,
+  0xf1, 0xee, 0xeb, 0xdb, 0xc7, 0x0d, 0xb4, 0xc4, 0xf8, 0x6c, 0x8b, 0x96,
+  0x30, 0xda, 0x08, 0xe9, 0x4d, 0x92, 0xd9, 0xe8, 0xf9, 0x81, 0xec, 0xc7,
+  0xb2, 0x40, 0x58, 0x19, 0x82, 0x72, 0xbb, 0xe7, 0xb2, 0x55, 0x58, 0x75,
+  0xe3, 0x2f, 0xda, 0x39, 0xa0, 0xf2, 0x6e, 0x71, 0x11, 0xed, 0x78, 0xe1,
+  0xde, 0xc6, 0x2b, 0x61, 0xba, 0xaa, 0x5b, 0x19, 0x65, 0xb6, 0x51, 0xcf,
+  0x6d, 0xaf, 0x4b, 0xc6, 0x6f, 0x3c, 0x43, 0x9f, 0x65, 0x72, 0x87, 0x6b,
+  0x12, 0xdb, 0xfa, 0x16, 0xf1, 0xea, 0x22, 0xf2, 0x28, 0x95, 0xb0, 0x5f,
+  0xb7, 0x57, 0xaf, 0x72, 0x47, 0x44, 0x07, 0x64, 0x15, 0x83, 0x84, 0x8e,
+  0x0f, 0x7a, 0x88, 0xf3, 0x11, 0x01, 0x9d, 0x6c, 0xc8, 0xb6, 0xc8, 0x20,
+  0x7c, 0xa3, 0x0c, 0x2f, 0x40, 0x27, 0x2a, 0xb7, 0xdb, 0x2d, 0xc4, 0xc5,
+  0x62, 0x51, 0x5d, 0xaf, 0xd7, 0x7c, 0x6c, 0xe2, 0x7d, 0x65, 0xea, 0x2a,
+  0x24, 0xbd, 0x79, 0xbd, 0x5e, 0xf9, 0xbc, 0xa1, 0x69, 0x9a, 0x7f, 0xa7,
+  0x22, 0xa4, 0xfd, 0x72, 0xb9, 0xf4, 0xb6, 0xc4, 0x3a, 0x73, 0xac, 0xbe,
+  0x42, 0x73, 0x49, 0x96, 0x4e, 0x56, 0xba, 0x18, 0xd6, 0x75, 0x5d, 0xed,
+  0x76, 0xbb, 0xb0, 0xdf, 0xef, 0xf3, 0x61, 0x84, 0x26, 0xb5, 0x4e, 0x49,
+  0x56, 0x4c, 0x73, 0x49, 0x86, 0x64, 0x3f, 0xef, 0x7e, 0x0f, 0x82, 0xe4,
+  0x98, 0xcf, 0xe7, 0x7f, 0xfa, 0xfc, 0x64, 0x45, 0x3c, 0x7e, 0x04, 0x18,
+  0x00, 0x80, 0xe2, 0x32, 0x3a, 0xb2, 0xea, 0x1b, 0xdf, 0x00, 0x00, 0x00,
+  0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82
+};
+unsigned int gamepad_grip_sense_png_len = 837;

BIN
test/gamepad_grip_sense.png


+ 90 - 0
test/gamepadutils.c

@@ -30,6 +30,7 @@
 #include "gamepad_button_background.h"
 #include "gamepad_button_background.h"
 #include "gamepad_wired.h"
 #include "gamepad_wired.h"
 #include "gamepad_wireless.h"
 #include "gamepad_wireless.h"
+#include "gamepad_grip_sense.h"
 
 
 #include <limits.h>
 #include <limits.h>
 
 
@@ -305,6 +306,17 @@ static const struct
     { 355, 200 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 */
     { 355, 200 }, /* SDL_GAMEPAD_BUTTON_LEFT_PADDLE2 */
 };
 };
 
 
+static const struct
+{
+    int x;
+    int y;
+} capsense_positions[] = {
+    {  97, 194 }, /* SDL_GAMEPAD_CAPSENSE_LEFT_STICK */
+    { 330, 270 }, /* SDL_GAMEPAD_CAPSENSE_RIGHT_STICK */
+    {  50, 260 }, /* SDL_GAMEPAD_CAPSENSE_LEFT_GRIP */
+    { 462, 260 }, /* SDL_GAMEPAD_CAPSENSE_RIGHT_GRIP */
+};
+
 /* This is indexed by gamepad element */
 /* This is indexed by gamepad element */
 static const struct
 static const struct
 {
 {
@@ -366,6 +378,7 @@ struct GamepadImage
     SDL_Texture *dual_touchpad_texture;
     SDL_Texture *dual_touchpad_texture;
     SDL_Texture *button_texture;
     SDL_Texture *button_texture;
     SDL_Texture *axis_texture;
     SDL_Texture *axis_texture;
+    SDL_Texture *grip_sense_texture;
     float gamepad_width;
     float gamepad_width;
     float gamepad_height;
     float gamepad_height;
     float face_width;
     float face_width;
@@ -382,6 +395,8 @@ struct GamepadImage
     float button_height;
     float button_height;
     float axis_width;
     float axis_width;
     float axis_height;
     float axis_height;
+    float grip_sense_width;
+    float grip_sense_height;
 
 
     float x;
     float x;
     float y;
     float y;
@@ -392,6 +407,7 @@ struct GamepadImage
     ControllerDisplayMode display_mode;
     ControllerDisplayMode display_mode;
 
 
     bool elements[SDL_GAMEPAD_ELEMENT_MAX];
     bool elements[SDL_GAMEPAD_ELEMENT_MAX];
+    bool capsense_elements[SDL_GAMEPAD_CAPSENSE_ELEMENT_MAX];
 
 
     SDL_JoystickConnectionState connection_state;
     SDL_JoystickConnectionState connection_state;
     SDL_PowerState battery_state;
     SDL_PowerState battery_state;
@@ -453,6 +469,10 @@ GamepadImage *CreateGamepadImage(SDL_Renderer *renderer)
         SDL_GetTextureSize(ctx->axis_texture, &ctx->axis_width, &ctx->axis_height);
         SDL_GetTextureSize(ctx->axis_texture, &ctx->axis_width, &ctx->axis_height);
         SDL_SetTextureColorMod(ctx->axis_texture, 10, 255, 21);
         SDL_SetTextureColorMod(ctx->axis_texture, 10, 255, 21);
 
 
+        ctx->grip_sense_texture = CreateTexture(renderer, gamepad_grip_sense_png, gamepad_grip_sense_png_len);
+        SDL_GetTextureSize(ctx->grip_sense_texture, &ctx->grip_sense_width, &ctx->grip_sense_height);
+        SDL_SetTextureColorMod(ctx->grip_sense_texture, 10, 255, 21);
+
         ctx->showing_front = true;
         ctx->showing_front = true;
     }
     }
     return ctx;
     return ctx;
@@ -680,6 +700,7 @@ void ClearGamepadImage(GamepadImage *ctx)
     }
     }
 
 
     SDL_zeroa(ctx->elements);
     SDL_zeroa(ctx->elements);
+    SDL_zeroa(ctx->capsense_elements);
 }
 }
 
 
 void SetGamepadImageElement(GamepadImage *ctx, int element, bool active)
 void SetGamepadImageElement(GamepadImage *ctx, int element, bool active)
@@ -691,6 +712,15 @@ void SetGamepadImageElement(GamepadImage *ctx, int element, bool active)
     ctx->elements[element] = active;
     ctx->elements[element] = active;
 }
 }
 
 
+static void SetGamepadCapSenseImageElement(GamepadImage *ctx, int capsense_element, bool active)
+{
+    if (!ctx) {
+        return;
+    }
+
+    ctx->capsense_elements[capsense_element] = active;
+}
+
 static void FreeTouchpads(GamepadImage *ctx)
 static void FreeTouchpads(GamepadImage *ctx)
 {
 {
     if (ctx->touchpads) {
     if (ctx->touchpads) {
@@ -729,6 +759,11 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad)
         SetGamepadImageElement(ctx, button, SDL_GetGamepadButton(gamepad, button));
         SetGamepadImageElement(ctx, button, SDL_GetGamepadButton(gamepad, button));
     }
     }
 
 
+    for (i = 0; i < SDL_GAMEPAD_CAPSENSE_COUNT; ++i) {
+        const SDL_GamepadCapSenseType capsense = (SDL_GamepadCapSenseType)i;
+        SetGamepadCapSenseImageElement(ctx, capsense, SDL_GetGamepadCapSense(gamepad, capsense));
+    }
+
     for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
     for (i = 0; i < SDL_GAMEPAD_AXIS_COUNT; ++i) {
         const SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
         const SDL_GamepadAxis axis = (SDL_GamepadAxis)i;
         const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */
         const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */
@@ -968,6 +1003,31 @@ void RenderGamepadImage(GamepadImage *ctx)
             }
             }
         }
         }
     }
     }
+
+    if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->showing_front) {
+        SDL_SetTextureAlphaMod(ctx->button_texture, SDL_ALPHA_OPAQUE / 2);
+        for (i = 0; i < 2; ++i) {
+            if (ctx->capsense_elements[i]) {
+                dst.w = ctx->button_width / 2;
+                dst.h = ctx->button_height / 2;
+                dst.x = ctx->x + capsense_positions[i].x - dst.w / 2;
+                dst.y = ctx->y + capsense_positions[i].y - dst.h / 2;
+                SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
+            }
+        }
+        SDL_SetTextureAlphaMod(ctx->button_texture, SDL_ALPHA_OPAQUE);
+        SDL_SetTextureAlphaMod(ctx->grip_sense_texture, SDL_ALPHA_OPAQUE / 2);
+        for (i = 2; i < SDL_arraysize(capsense_positions); ++i) {
+            if (ctx->capsense_elements[i]) {
+                dst.w = ctx->grip_sense_width;
+                dst.h = ctx->grip_sense_height;
+                dst.x = ctx->x + capsense_positions[i].x - dst.w / 2;
+                dst.y = ctx->y + capsense_positions[i].y - dst.h / 2;
+                SDL_RenderTexture(ctx->renderer, ctx->grip_sense_texture, NULL, &dst);
+            }
+        }
+        SDL_SetTextureAlphaMod(ctx->grip_sense_texture, SDL_ALPHA_OPAQUE);
+    }
 }
 }
 
 
 void DestroyGamepadImage(GamepadImage *ctx)
 void DestroyGamepadImage(GamepadImage *ctx)
@@ -1033,6 +1093,14 @@ static const char *gamepad_axis_names[] = {
 };
 };
 SDL_COMPILE_TIME_ASSERT(gamepad_axis_names, SDL_arraysize(gamepad_axis_names) == SDL_GAMEPAD_AXIS_COUNT);
 SDL_COMPILE_TIME_ASSERT(gamepad_axis_names, SDL_arraysize(gamepad_axis_names) == SDL_GAMEPAD_AXIS_COUNT);
 
 
+static const char *capsense_names[] = {
+    "L.Stick Touch",
+    "R.Stick Touch",
+    "L.Grip Sense",
+    "R.Grip Sense",
+};
+SDL_COMPILE_TIME_ASSERT(capsense_names, SDL_arraysize(capsense_names) == SDL_GAMEPAD_CAPSENSE_COUNT);
+
 struct GamepadDisplay
 struct GamepadDisplay
 {
 {
     SDL_Renderer *renderer;
     SDL_Renderer *renderer;
@@ -1714,6 +1782,28 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad)
             }
             }
         }
         }
 
 
+        for (i = 0; i < SDL_GAMEPAD_CAPSENSE_COUNT; i++) {
+            const SDL_GamepadCapSenseType capsense = (SDL_GamepadCapSenseType)i;
+            if (SDL_GamepadHasCapSense(gamepad, capsense)) {
+                SDL_snprintf(text, sizeof(text), "%s:", capsense_names[i]);
+                SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text);
+
+                if (SDL_GetGamepadCapSense(gamepad, capsense)) {
+                    SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21);
+                } else {
+                    SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255);
+                }
+
+                dst.x = x + center + 2.0f;
+                dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2;
+                dst.w = ctx->button_width;
+                dst.h = ctx->button_height;
+                SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst);
+
+                y += ctx->button_height + 2.0f;
+            };
+        }
+
         has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
         has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL);
         has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
         has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO);
 
 

+ 9 - 0
test/gamepadutils.h

@@ -46,6 +46,15 @@ enum
     SDL_GAMEPAD_ELEMENT_MAX,
     SDL_GAMEPAD_ELEMENT_MAX,
 };
 };
 
 
+enum
+{
+    SDL_GAMEPAD_CAPSENSE_ELEMENT_LEFT_STICK,
+    SDL_GAMEPAD_CAPSENSE_ELEMENT_RIGHT_STICK,
+    SDL_GAMEPAD_CAPSENSE_ELEMENT_LEFT_GRIP,
+    SDL_GAMEPAD_CAPSENSE_ELEMENT_RIGHT_GRIP,
+    SDL_GAMEPAD_CAPSENSE_ELEMENT_MAX
+};
+
 #define HIGHLIGHT_COLOR         224, 255, 255, SDL_ALPHA_OPAQUE
 #define HIGHLIGHT_COLOR         224, 255, 255, SDL_ALPHA_OPAQUE
 #define HIGHLIGHT_TEXTURE_MOD   224, 255, 255
 #define HIGHLIGHT_TEXTURE_MOD   224, 255, 255
 #define PRESSED_COLOR           175, 238, 238, SDL_ALPHA_OPAQUE
 #define PRESSED_COLOR           175, 238, 238, SDL_ALPHA_OPAQUE

+ 10 - 0
test/testcontroller.c

@@ -2213,6 +2213,16 @@ SDL_AppResult SDLCALL SDL_AppEvent(void *appstate, SDL_Event *event)
         }
         }
         break;
         break;
 
 
+    case SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH:
+    case SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE:
+#ifdef VERBOSE_CAPSENSE
+        SDL_Log("Gamepad %" SDL_PRIu32 " capsense %u %s",
+                event->gcapsense.which,
+                event->gcapsense.capsense,
+                event->gcapsense.down ? "touch" : "release");
+#endif /* VERBOSE_CAPSENSE */
+        break;
+
     case SDL_EVENT_MOUSE_BUTTON_DOWN:
     case SDL_EVENT_MOUSE_BUTTON_DOWN:
         if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
         if (virtual_joystick && controller && controller->joystick == virtual_joystick) {
             VirtualGamepadMouseDown(event->button.x, event->button.y);
             VirtualGamepadMouseDown(event->button.x, event->button.y);

+ 1 - 0
test/testutils.c

@@ -47,6 +47,7 @@ static const struct
     { "gamepad_axis_arrow.png", "GP_AXARW.PNG" },
     { "gamepad_axis_arrow.png", "GP_AXARW.PNG" },
     { "gamepad_wired.png", "GP_WIRED.PNG" },
     { "gamepad_wired.png", "GP_WIRED.PNG" },
     { "gamepad_wireless.png", "GP_WLESS.PNG" },
     { "gamepad_wireless.png", "GP_WLESS.PNG" },
+    { "gamepad_grip_sense.png", "GP_GRIPS.PNG" },
     { "sdl-test_round.png", "SDLROUND.PNG" },
     { "sdl-test_round.png", "SDLROUND.PNG" },
     { NULL, NULL }
     { NULL, NULL }
 };
 };