From c54a017f47f79a9bbf14d06c5691d6d6d397e860 Mon Sep 17 00:00:00 2001
From: Vicki Pfau <[EMAIL REDACTED]>
Date: Mon, 2 Jun 2025 22:36:50 -0700
Subject: [PATCH] joystick: Clean up Elite Button handling
---
src/joystick/hidapi/SDL_hidapi_gip.c | 215 +++++++++++++++++----------
1 file changed, 138 insertions(+), 77 deletions(-)
diff --git a/src/joystick/hidapi/SDL_hidapi_gip.c b/src/joystick/hidapi/SDL_hidapi_gip.c
index b72097ae138b8..4b4afcc02404e 100644
--- a/src/joystick/hidapi/SDL_hidapi_gip.c
+++ b/src/joystick/hidapi/SDL_hidapi_gip.c
@@ -90,6 +90,9 @@
#define GIP_CMD_GUIDE_COLOR 0x0e
#define GIP_SL_ELITE_CONFIG 0x4d
+#define GIP_BTN_OFFSET_XBE1 28
+#define GIP_BTN_OFFSET_XBE2 14
+
#define GIP_FLAG_FRAGMENT (1u << 7)
#define GIP_FLAG_INIT_FRAG (1u << 6)
#define GIP_FLAG_SYSTEM (1u << 5)
@@ -269,11 +272,12 @@ typedef enum
typedef enum
{
- GIP_PADDLES_UNKNOWN,
- GIP_PADDLES_XBE1,
- GIP_PADDLES_XBE2_RAW,
- GIP_PADDLES_XBE2,
-} GIP_PaddleFormat;
+ GIP_BTN_FMT_UNKNOWN,
+ GIP_BTN_FMT_XBE1,
+ GIP_BTN_FMT_XBE2_RAW,
+ GIP_BTN_FMT_XBE2_4,
+ GIP_BTN_FMT_XBE2_5,
+} GIP_EliteButtonFormat;
/* These come across the wire as little-endian, so let's store them in-memory as such so we can memcmp */
#define MAKE_GUID(NAME, A, B, C, D0, D1, D2, D3, D4, D5, D6, D7) \
@@ -471,12 +475,11 @@ typedef struct GIP_Attachment
int altcode_digit;
GIP_AttachmentType attachment_type;
- GIP_PaddleFormat paddle_format;
+ GIP_EliteButtonFormat xbe_format;
Uint32 features;
Uint32 quirks;
Uint8 share_button_idx;
Uint8 paddle_idx;
- int paddle_offset;
Uint8 extra_button_idx;
int extra_buttons;
@@ -1114,9 +1117,30 @@ static bool GIP_FragmentFailed(GIP_Attachment *attachment, const GIP_Header *hea
}
static bool GIP_EnableEliteButtons(GIP_Attachment *attachment) {
- if (attachment->paddle_format == GIP_PADDLES_XBE2_RAW ||
- (attachment->firmware_major_version != 4 && attachment->firmware_minor_version < 17))
- {
+ if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT) {
+ if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) {
+ attachment->xbe_format = GIP_BTN_FMT_XBE1;
+ } else if (attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) {
+ if (attachment->firmware_major_version == 4) {
+ attachment->xbe_format = GIP_BTN_FMT_XBE2_4;
+ } else if (attachment->firmware_major_version == 5) {
+ /*
+ * The exact range for this being necessary is unknown, but it
+ * starts at 5.11 and at either 5.16 or 5.17. This approach
+ * still works on 5.21, even if it's not necessary, so having
+ * a loose upper limit is fine.
+ */
+ if (attachment->firmware_minor_version >= 11 &&
+ attachment->firmware_minor_version < 17)
+ {
+ attachment->xbe_format = GIP_BTN_FMT_XBE2_RAW;
+ } else {
+ attachment->xbe_format = GIP_BTN_FMT_XBE2_5;
+ }
+ }
+ }
+ }
+ if (attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
/*
* The meaning of this packet is unknown and not documented, but it's
* needed for the Elite 2 controller to send raw reports
@@ -1182,10 +1206,9 @@ static bool GIP_SendInitSequence(GIP_Attachment *attachment)
{
return false;
}
-
- if (!GIP_EnableEliteButtons(attachment)) {
- return false;
- }
+ }
+ if (!GIP_EnableEliteButtons(attachment)) {
+ return false;
}
if (!GIP_SendSetDeviceState(attachment, GIP_STATE_START)) {
return false;
@@ -1661,11 +1684,6 @@ static bool GIP_HandleCommandFirmware(
if (attachment->device->device->vendor_id == USB_VENDOR_MICROSOFT &&
attachment->device->device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2)
{
- if (attachment->firmware_major_version == 5 && attachment->firmware_minor_version < 17) {
- attachment->paddle_format = GIP_PADDLES_XBE2_RAW;
- } else {
- attachment->paddle_format = GIP_PADDLES_XBE2;
- }
return GIP_EnableEliteButtons(attachment);
}
return true;
@@ -1694,28 +1712,47 @@ static bool GIP_HandleCommandRawReport(
return true;
}
- if (num_bytes < 17 || num_bytes <= attachment->paddle_offset) {
+ if (num_bytes < 17) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short raw report");
return false;
}
- if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->paddle_format == GIP_PADDLES_XBE2_RAW) {
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx,
- (bytes[attachment->paddle_offset] & 0x01) != 0);
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx + 1,
- (bytes[attachment->paddle_offset] & 0x02) != 0);
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx + 2,
- (bytes[attachment->paddle_offset] & 0x04) != 0);
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx + 3,
- (bytes[attachment->paddle_offset] & 0x08) != 0);
+ if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) && attachment->xbe_format == GIP_BTN_FMT_XBE2_RAW) {
+ if (bytes[15] & 3) {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx,
+ 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 1,
+ 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 2,
+ 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 3,
+ 0);
+ } else {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 1,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 2,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 3,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0);
+ }
}
return true;
}
@@ -2080,46 +2117,78 @@ static bool GIP_HandleLLInputReport(
break;
}
- if ((attachment->features & GIP_FEATURE_ELITE_BUTTONS) &&
- num_bytes > attachment->paddle_offset &&
- attachment->last_input[attachment->paddle_offset] != bytes[attachment->paddle_offset])
- {
- if (attachment->paddle_format == GIP_PADDLES_XBE1) {
- if (bytes[attachment->paddle_offset] & 0x10) {
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx,
- (bytes[attachment->paddle_offset] & 0x02) != 0);
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx + 1,
- (bytes[attachment->paddle_offset] & 0x08) != 0);
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx + 2,
- (bytes[attachment->paddle_offset] & 0x01) != 0);
- SDL_SendJoystickButton(timestamp,
- joystick,
- attachment->paddle_idx + 3,
- (bytes[attachment->paddle_offset] & 0x04) != 0);
+ if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
+ bool clear = false;
+ if (attachment->xbe_format == GIP_BTN_FMT_XBE1 &&
+ num_bytes > GIP_BTN_OFFSET_XBE1 &&
+ attachment->last_input[GIP_BTN_OFFSET_XBE1] != bytes[GIP_BTN_OFFSET_XBE1] &&
+ (bytes[GIP_BTN_OFFSET_XBE1] & 0x10))
+ {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx,
+ (bytes[GIP_BTN_OFFSET_XBE1] & 0x02) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 1,
+ (bytes[GIP_BTN_OFFSET_XBE1] & 0x08) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 2,
+ (bytes[GIP_BTN_OFFSET_XBE1] & 0x01) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 3,
+ (bytes[GIP_BTN_OFFSET_XBE1] & 0x04) != 0);
+ } else if ((attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ||
+ attachment->xbe_format == GIP_BTN_FMT_XBE2_5) &&
+ num_bytes > GIP_BTN_OFFSET_XBE2)
+ {
+ int profile_offset = attachment->xbe_format == GIP_BTN_FMT_XBE2_4 ? 15 : 20;
+ if (attachment->last_input[GIP_BTN_OFFSET_XBE2] != bytes[GIP_BTN_OFFSET_XBE2] ||
+ attachment->last_input[profile_offset] != bytes[profile_offset])
+ {
+ if (bytes[profile_offset] & 3) {
+ clear = true;
+ } else {
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x01) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 1,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x02) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 2,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x04) != 0);
+ SDL_SendJoystickButton(timestamp,
+ joystick,
+ attachment->paddle_idx + 3,
+ (bytes[GIP_BTN_OFFSET_XBE2] & 0x08) != 0);
+ }
}
- } else if (attachment->paddle_format == GIP_PADDLES_XBE2) {
+ } else {
+ clear = true;
+ }
+ if (clear) {
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx,
- (bytes[attachment->paddle_offset] & 0x01) != 0);
+ 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 1,
- (bytes[attachment->paddle_offset] & 0x02) != 0);
+ 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 2,
- (bytes[attachment->paddle_offset] & 0x04) != 0);
+ 0);
SDL_SendJoystickButton(timestamp,
joystick,
attachment->paddle_idx + 3,
- (bytes[attachment->paddle_offset] & 0x08) != 0);
+ 0);
}
}
@@ -2593,19 +2662,11 @@ static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic
// Initialize the joystick capabilities
joystick->nbuttons = 11;
- if (device->vendor_id == USB_VENDOR_MICROSOFT) {
- if (device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_1) {
- attachment->paddle_offset = 28;
- attachment->paddle_format = GIP_PADDLES_XBE1;
- } else if (device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2) {
- attachment->paddle_offset = 14;
- attachment->paddle_format = GIP_PADDLES_XBE2;
- if (attachment->firmware_major_version == 5 && attachment->firmware_minor_version < 17) {
- attachment->paddle_format = GIP_PADDLES_XBE2_RAW;
- }
- }
- }
- if (attachment->paddle_offset > 0) {
+ GIP_EnableEliteButtons(attachment);
+ if (attachment->xbe_format != GIP_BTN_FMT_UNKNOWN ||
+ (device->vendor_id == USB_VENDOR_MICROSOFT &&
+ device->product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2))
+ {
attachment->paddle_idx = (Uint8) joystick->nbuttons;
joystick->nbuttons += 4;
}