From 5b98c1cc2f598115906c9c1f2758d3d256913468 Mon Sep 17 00:00:00 2001
From: ceski <[EMAIL REDACTED]>
Date: Sun, 17 May 2026 08:26:29 -0700
Subject: [PATCH] Add capacitive sense gamepad events (#15627)
---
include/SDL3/SDL_events.h | 20 ++++
include/SDL3/SDL_gamepad.h | 48 ++++++++++
src/dynapi/SDL_dynapi.exports | 2 +
src/dynapi/SDL_dynapi.sym | 2 +
src/dynapi/SDL_dynapi_overrides.h | 2 +
src/dynapi/SDL_dynapi_procs.h | 2 +
src/events/SDL_categories.c | 4 +
src/events/SDL_categories_c.h | 1 +
src/events/SDL_events.c | 12 +++
src/joystick/SDL_gamepad.c | 50 +++++++++-
src/joystick/SDL_gamepad_db.h | 2 +-
src/joystick/SDL_joystick.c | 67 +++++++++++++
src/joystick/SDL_joystick_c.h | 2 +
src/joystick/SDL_sysjoystick.h | 9 ++
src/joystick/hidapi/SDL_hidapi_steam_hori.c | 10 +-
src/joystick/hidapi/SDL_hidapi_steam_triton.c | 25 ++---
src/joystick/hidapi/SDL_hidapi_steamdeck.c | 13 +--
src/test/SDL_test_common.c | 27 ++++++
test/CMakeLists.txt | 3 +-
test/gamepad_grip_sense.h | 73 ++++++++++++++
test/gamepad_grip_sense.png | Bin 0 -> 837 bytes
test/gamepadutils.c | 90 ++++++++++++++++++
test/gamepadutils.h | 9 ++
test/testcontroller.c | 10 ++
test/testutils.c | 1 +
25 files changed, 457 insertions(+), 27 deletions(-)
create mode 100644 test/gamepad_grip_sense.h
create mode 100644 test/gamepad_grip_sense.png
diff --git a/include/SDL3/SDL_events.h b/include/SDL3/SDL_events.h
index ed6950ad3b1d0..dd822ecc71936 100644
--- a/include/SDL3/SDL_events.h
+++ b/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_UPDATE_COMPLETE, /**< Gamepad update is complete */
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 */
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 */
} 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.*)
*
@@ -1040,6 +1059,7 @@ typedef union SDL_Event
SDL_GamepadButtonEvent gbutton; /**< Gamepad button event data */
SDL_GamepadTouchpadEvent gtouchpad; /**< Gamepad touchpad event data */
SDL_GamepadSensorEvent gsensor; /**< Gamepad sensor event data */
+ SDL_GamepadCapSenseEvent gcapsense; /**< Gamepad capsense event data */
SDL_AudioDeviceEvent adevice; /**< Audio device event data */
SDL_CameraDeviceEvent cdevice; /**< Camera device event data */
SDL_SensorEvent sensor; /**< Sensor event data */
diff --git a/include/SDL3/SDL_gamepad.h b/include/SDL3/SDL_gamepad.h
index 9c88a770ae063..2d0e83440ad70 100644
--- a/include/SDL3/SDL_gamepad.h
+++ b/include/SDL3/SDL_gamepad.h
@@ -231,6 +231,24 @@ typedef enum SDL_GamepadAxis
SDL_GAMEPAD_AXIS_COUNT
} 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.
*
@@ -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);
+/**
+ * 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.
*
diff --git a/src/dynapi/SDL_dynapi.exports b/src/dynapi/SDL_dynapi.exports
index 32e9fbff861c1..9864557071c3e 100644
--- a/src/dynapi/SDL_dynapi.exports
+++ b/src/dynapi/SDL_dynapi.exports
@@ -1288,3 +1288,5 @@ _SDL_IsPhone
_SDL_LoadJPG_IO
_SDL_LoadJPG
_SDL_HasSVE2
+_SDL_GamepadHasCapSense
+_SDL_GetGamepadCapSense
diff --git a/src/dynapi/SDL_dynapi.sym b/src/dynapi/SDL_dynapi.sym
index ca1a1c97d940e..3958a52aa60af 100644
--- a/src/dynapi/SDL_dynapi.sym
+++ b/src/dynapi/SDL_dynapi.sym
@@ -1289,6 +1289,8 @@ SDL3_0.0.0 {
SDL_LoadJPG_IO;
SDL_LoadJPG;
SDL_HasSVE2;
+ SDL_GamepadHasCapSense;
+ SDL_GetGamepadCapSense;
# extra symbols go here (don't modify this line)
local: *;
};
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 677768ff2f107..b54d32ae6dcf5 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -1315,3 +1315,5 @@
#define SDL_LoadJPG_IO SDL_LoadJPG_IO_REAL
#define SDL_LoadJPG SDL_LoadJPG_REAL
#define SDL_HasSVE2 SDL_HasSVE2_REAL
+#define SDL_GamepadHasCapSense SDL_GamepadHasCapSense_REAL
+#define SDL_GetGamepadCapSense SDL_GetGamepadCapSense_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index 99899b346e9a6..4f8ac0ba0cbb4 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/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,(const char *a),(a),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)
diff --git a/src/events/SDL_categories.c b/src/events/SDL_categories.c
index fa120775b2ba2..9d7722923b761 100644
--- a/src/events/SDL_categories.c
+++ b/src/events/SDL_categories.c
@@ -134,6 +134,10 @@ SDL_EventCategory SDL_GetEventCategory(Uint32 type)
case SDL_EVENT_GAMEPAD_SENSOR_UPDATE:
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_UP:
case SDL_EVENT_FINGER_CANCELED:
diff --git a/src/events/SDL_categories_c.h b/src/events/SDL_categories_c.h
index a1259e0e90a13..a3762746d53b8 100644
--- a/src/events/SDL_categories_c.h
+++ b/src/events/SDL_categories_c.h
@@ -49,6 +49,7 @@ typedef enum SDL_EventCategory
SDL_EVENTCATEGORY_GBUTTON,
SDL_EVENTCATEGORY_GTOUCHPAD,
SDL_EVENTCATEGORY_GSENSOR,
+ SDL_EVENTCATEGORY_GCAPSENSE,
SDL_EVENTCATEGORY_ADEVICE,
SDL_EVENTCATEGORY_CDEVICE,
SDL_EVENTCATEGORY_SENSOR,
diff --git a/src/events/SDL_events.c b/src/events/SDL_events.c
index 315a8b6d905a9..19f5a85adc95d 100644
--- a/src/events/SDL_events.c
+++ b/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]);
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) \
(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, \
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 3b0b86fdfb8df..b9c3ade4f0bdd 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/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));
} 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
- 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)) {
// 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) ||
SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
// 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)) {
/* 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)) {
SDL_strlcat(mapping_string, "paddle1:b11,paddle2:b12,paddle3:b13,paddle4:b14,", sizeof(mapping_string));
if (guid.data[15] >= SDL_FLYDIGI_VADER2) {
@@ -3952,6 +3952,48 @@ bool SDL_GetGamepadSensorData(SDL_Gamepad *gamepad, SDL_SensorType type, float *
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_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_UP,
SDL_EVENT_GAMEPAD_SENSOR_UPDATE,
+ SDL_EVENT_GAMEPAD_CAPSENSE_TOUCH,
+ SDL_EVENT_GAMEPAD_CAPSENSE_RELEASE,
};
void SDL_SetGamepadEventsEnabled(bool enabled)
diff --git a/src/joystick/SDL_gamepad_db.h b/src/joystick/SDL_gamepad_db.h
index 5859e6d373d0b..6b1a95409855f 100644
--- a/src/joystick/SDL_gamepad_db.h
+++ b/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,",
"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,",
- "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,",
"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,",
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index f052e2d04a987..746cb04690a53 100644
--- a/src/joystick/SDL_joystick.c
+++ b/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)
{
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)
{
Uint32 entry;
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 62e1f9f86504b..b80fb6b6512c9 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/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_PrivateJoystickAddSensor(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 bool SDL_IsJoystickBeingAdded(void);
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_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_SendJoystickCapSense(Uint64 timestamp, SDL_Joystick *joystick, SDL_GamepadCapSenseType type, bool down);
extern void SDL_SendJoystickPowerInfo(SDL_Joystick *joystick, SDL_PowerState state, int percent);
// Function to get the Steam virtual gamepad info for a joystick
diff --git a/src/joystick/SDL_sysjoystick.h b/src/joystick/SDL_sysjoystick.h
index 15659b8cc09fc..76bce9006d762 100644
--- a/src/joystick/SDL_sysjoystick.h
+++ b/src/joystick/SDL_sysjoystick.h
@@ -72,6 +72,12 @@ typedef struct SDL_JoystickSensorInfo
float data[3]; // If this needs to expand, update SDL_GamepadSensorEvent
} SDL_JoystickSensorInfo;
+typedef struct SDL_JoystickCapSenseInfo
+{
+ SDL_GamepadCapSenseType type;
+ bool down;
+} SDL_JoystickCapSenseInfo;
+
#define _guarded SDL_GUARDED_BY(SDL_joystick_lock)
struct SDL_Joystick
@@ -105,6 +111,9 @@ struct SDL_Joystick
int nsensors_enabled _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 high_frequency_rumble _guarded;
Uint64 rumble_expiration _guarded;
diff --git a/src/joystick/hidapi/SDL_hidapi_steam_hori.c b/src/joystick/hidapi/SDL_hidapi_steam_hori.c
index c30b7cef0cf07..c24602f7fe8a2 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam_hori.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam_hori.c
@@ -39,8 +39,6 @@ enum
SDL_GAMEPAD_BUTTON_HORI_FL,
SDL_GAMEPAD_BUTTON_HORI_M1,
SDL_GAMEPAD_BUTTON_HORI_M2,
- SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_L,
- SDL_GAMEPAD_BUTTON_HORI_JOYSTICK_TOUCH_R,
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
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;
}
@@ -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_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_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_FL, ((data[7] & 0x80) != 0));
}
diff --git a/src/joystick/hidapi/SDL_hidapi_steam_triton.c b/src/joystick/hidapi/SDL_hidapi_steam_triton.c
index f1c58812291fd..97152d359cf78 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam_triton.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam_triton.c
@@ -47,10 +47,6 @@ enum
SDL_GAMEPAD_BUTTON_TRITON_LEFT_PADDLE2,
SDL_GAMEPAD_BUTTON_TRITON_RIGHT_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,
};
@@ -187,15 +183,15 @@ static void HIDAPI_DriverSteamTriton_HandleState(SDL_HIDAPI_Device *device,
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_TRITON_LEFT_TOUCHPAD,
((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) {
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_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;
}
diff --git a/src/joystick/hidapi/SDL_hidapi_steamdeck.c b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
index 92ca8402a9641..286b7c97ea8dc 100644
--- a/src/joystick/hidapi/SDL_hidapi_steamdeck.c
+++ b/src/joystick/hidapi/SDL_hidapi_steamdeck.c
@@ -41,8 +41,6 @@ enum
SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_PADDLE2,
SDL_GAMEPAD_BUTTON_STEAM_DECK_RIGHT_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,
};
@@ -202,10 +200,10 @@ static void HIDAPI_DriverSteamDeck_HandleState(SDL_HIDAPI_Device *device,
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_STEAM_DECK_LEFT_TOUCHPAD,
((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) {
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_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_LEFT_STICK);
+ SDL_PrivateJoystickAddCapSense(joystick, SDL_GAMEPAD_CAPSENSE_RIGHT_STICK);
+
return true;
}
diff --git a/src/test/SDL_test_common.c b/src/test/SDL_test_common.c
index 460d05d02752b..ee324a7a35795 100644
--- a/src/test/SDL_test_common.c
+++ b/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)
{
switch (event->type) {
@@ -1878,6 +1895,16 @@ void SDLTest_PrintEvent(const SDL_Event *event)
event->gbutton.which, event->gbutton.button,
GamepadButtonName((SDL_GamepadButton)event->gbutton.button));
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.
(Patch may be truncated, please check the link at the top of this post.)