extract capabilities for 360 controllers over libusb (#15183)

Read capabilities when using xinput controllers via the libusb backend

This gives us access to the subtype on linux and macOS, and gives us a lot of data we can use for handling more detailed device types when I look into a unified api for exposing instrument data later.
This commit is contained in:
Sanjay Govind
2026-03-09 13:29:56 +13:00
committed by GitHub
parent 466ab45722
commit 101273f429
3 changed files with 208 additions and 1 deletions

View File

@@ -23,9 +23,11 @@
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
#include "../../misc/SDL_libusb.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "SDL_hidapi_xbox360.h"
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
@@ -42,6 +44,7 @@ typedef struct
SDL_Joystick *joystick;
int player_index;
bool player_lights;
SDL_xinput_capabilities capabilities;
Uint8 last_state[USB_PACKET_LENGTH];
#ifdef SDL_PLATFORM_MACOS
bool controlled_by_360controller;
@@ -82,6 +85,103 @@ static bool IsControlledBy360ControllerDriverMacOS(SDL_HIDAPI_Device *device)
}
#endif
#ifdef HAVE_LIBUSB
static void FetchXInputCapabilities(SDL_HIDAPI_Device *device)
{
SDL_DriverXbox360_Context *ctx = (SDL_DriverXbox360_Context *)device->context;
SDL_LibUSBContext *libusb_ctx;
if (SDL_InitLibUSB(&libusb_ctx)) {
libusb_device_handle *handle = (libusb_device_handle *)SDL_GetPointerProperty(SDL_hid_get_properties(device->dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, NULL);
if (handle == NULL) {
SDL_QuitLibUSB();
return;
}
libusb_device *dev = libusb_ctx->get_device(handle);
if (dev == NULL) {
SDL_QuitLibUSB();
return;
}
struct libusb_config_descriptor *conf_desc = NULL;
const struct libusb_interface_descriptor *intf_desc;
libusb_ctx->get_active_config_descriptor(dev, &conf_desc);
if (conf_desc == NULL || conf_desc->bNumInterfaces < device->interface_number) {
SDL_QuitLibUSB();
return;
}
const struct libusb_interface *intf = &conf_desc->interface[device->interface_number];
intf_desc = &intf->altsetting[0];
if (intf_desc->extra_length == 17 && intf_desc->extra[1] == 0x21) {
ctx->capabilities.type = intf_desc->extra[3];
ctx->capabilities.subType = intf_desc->extra[4];
switch (ctx->capabilities.subType) {
case 0x01: // XINPUT_DEVSUBTYPE_GAMEPAD
device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
break;
case 0x02: // XINPUT_DEVSUBTYPE_WHEEL
device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
break;
case 0x03: // XINPUT_DEVSUBTYPE_ARCADE_STICK
device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
break;
case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK
device->joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
break;
case 0x05: // XINPUT_DEVSUBTYPE_DANCE_PAD
device->joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;
break;
case 0x06: // XINPUT_DEVSUBTYPE_GUITAR
case 0x07: // XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE
case 0x0B: // XINPUT_DEVSUBTYPE_GUITAR_BASS
device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
break;
case 0x08: // XINPUT_DEVSUBTYPE_DRUM_KIT
device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
break;
case 0x13: // XINPUT_DEVSUBTYPE_ARCADE_PAD
device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_PAD;
break;
default:
break;
}
device->guid.data[15] = ctx->capabilities.subType;
unsigned char buf[20];
int ret = libusb_ctx->control_transfer(handle, 0xC1, 0x01, 0x100, 0x0, buf, sizeof(buf), 100);
if (ret == sizeof(buf)) {
ctx->capabilities.flags = LOAD16(buf[18], buf[19]);
ctx->capabilities.gamepad.wButtons = LOAD16(buf[2], buf[3]);
ctx->capabilities.gamepad.bLeftTrigger = buf[4];
ctx->capabilities.gamepad.bRightTrigger = buf[5];
ctx->capabilities.gamepad.sThumbLX = LOAD16(buf[6], buf[7]);
ctx->capabilities.gamepad.sThumbLY = LOAD16(buf[8], buf[9]);
ctx->capabilities.gamepad.sThumbRX = LOAD16(buf[10], buf[11]);
ctx->capabilities.gamepad.sThumbRY = LOAD16(buf[12], buf[13]);
}
ret = libusb_ctx->control_transfer(handle, 0xC1, 0x01, 0x00, 0x0, buf, 8, 100);
if (ret == 8) {
ctx->capabilities.vibration.wLeftMotorSpeed = buf[3] << 8;
ctx->capabilities.vibration.wRightMotorSpeed = buf[4] << 8;
}
#ifdef DEBUG_XBOX_PROTOCOL
SDL_Log("Xbox 360 capabilities:");
SDL_Log(" type: %02x", ctx->capabilities.type);
SDL_Log(" subType: %02x", ctx->capabilities.subType);
SDL_Log(" flags: %04x", ctx->capabilities.flags);
SDL_Log(" wButtons: %02x", ctx->capabilities.gamepad.wButtons);
SDL_Log(" bLeftTrigger: %02x", ctx->capabilities.gamepad.bLeftTrigger);
SDL_Log(" bRightTrigger: %02x", ctx->capabilities.gamepad.bRightTrigger);
SDL_Log(" sThumbLX: %02x", ctx->capabilities.gamepad.sThumbLX);
SDL_Log(" sThumbLY: %02x", ctx->capabilities.gamepad.sThumbLY);
SDL_Log(" sThumbRX: %02x", ctx->capabilities.gamepad.sThumbRX);
SDL_Log(" sThumbRY: %02x", ctx->capabilities.gamepad.sThumbRY);
SDL_Log(" wLeftMotorSpeed: %02x", ctx->capabilities.vibration.wLeftMotorSpeed);
SDL_Log(" wRightMotorSpeed: %02x", ctx->capabilities.vibration.wRightMotorSpeed);
#endif
}
SDL_QuitLibUSB();
}
}
#endif
static bool HIDAPI_DriverXbox360_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
const int XB360W_IFACE_PROTOCOL = 129; // Wireless
@@ -227,7 +327,9 @@ static bool HIDAPI_DriverXbox360_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joy
joystick->nbuttons = 11;
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
#ifdef HAVE_LIBUSB
FetchXInputCapabilities(device);
#endif
return true;
}

View File

@@ -0,0 +1,27 @@
#include "SDL_internal.h"
#define FLAG_FORCE_FEEDBACK 0x01
#define FLAG_WIRELESS 0x02
#define FLAG_VOICE 0x04
#define FLAG_PLUGIN_MODULES 0x08
#define FLAG_NO_NAVIGATION 0x10
typedef struct {
uint8_t type;
uint8_t subType;
uint16_t flags;
struct {
uint16_t wButtons;
uint8_t bLeftTrigger;
uint8_t bRightTrigger;
int16_t sThumbLX;
int16_t sThumbLY;
int16_t sThumbRX;
int16_t sThumbRY;
} gamepad;
struct {
uint16_t wLeftMotorSpeed;
uint16_t wRightMotorSpeed;
} vibration;
} SDL_xinput_capabilities;

View File

@@ -26,6 +26,7 @@
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "SDL_hidapi_xbox360.h"
#ifdef SDL_JOYSTICK_HIDAPI_XBOX360
@@ -38,6 +39,7 @@ typedef struct
bool connected;
int player_index;
bool player_lights;
SDL_xinput_capabilities capabilities;
Uint8 last_state[USB_PACKET_LENGTH];
} SDL_DriverXbox360W_Context;
@@ -179,6 +181,18 @@ static bool HIDAPI_DriverXbox360W_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Jo
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
joystick->connection_state = SDL_JOYSTICK_CONNECTION_WIRELESS;
ctx->capabilities.type = 1;
ctx->capabilities.flags = FLAG_WIRELESS;
ctx->capabilities.subType = SDL_JOYSTICK_TYPE_GAMEPAD;
ctx->capabilities.gamepad.wButtons = 0xFFFF;
ctx->capabilities.gamepad.bLeftTrigger = 0xFF;
ctx->capabilities.gamepad.bRightTrigger = 0xFF;
ctx->capabilities.gamepad.sThumbLX = 0xFFC0;
ctx->capabilities.gamepad.sThumbLY = 0xFFC0;
ctx->capabilities.gamepad.sThumbRX = 0xFFC0;
ctx->capabilities.gamepad.sThumbRY = 0xFFC0;
ctx->capabilities.vibration.wLeftMotorSpeed = 0xFFFF;
ctx->capabilities.vibration.wRightMotorSpeed = 0xFFFF;
return true;
}
@@ -328,6 +342,44 @@ static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
if (joystick) {
UpdatePowerLevel(joystick, data[17]);
}
ctx->capabilities.type = 1;
ctx->capabilities.subType = data[25] & 0x7f;
if ((data[25] & 0x80) != 0) {
ctx->capabilities.flags |= FLAG_FORCE_FEEDBACK;
}
switch (data[25] & 0x7f) {
case 0x01: // XINPUT_DEVSUBTYPE_GAMEPAD
device->joystick_type = SDL_JOYSTICK_TYPE_GAMEPAD;
break;
case 0x02: // XINPUT_DEVSUBTYPE_WHEEL
device->joystick_type = SDL_JOYSTICK_TYPE_WHEEL;
break;
case 0x03: // XINPUT_DEVSUBTYPE_ARCADE_STICK
device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_STICK;
break;
case 0x04: // XINPUT_DEVSUBTYPE_FLIGHT_STICK
device->joystick_type = SDL_JOYSTICK_TYPE_FLIGHT_STICK;
break;
case 0x05: // XINPUT_DEVSUBTYPE_DANCE_PAD
device->joystick_type = SDL_JOYSTICK_TYPE_DANCE_PAD;
break;
case 0x06: // XINPUT_DEVSUBTYPE_GUITAR
case 0x07: // XINPUT_DEVSUBTYPE_GUITAR_ALTERNATE
case 0x0B: // XINPUT_DEVSUBTYPE_GUITAR_BASS
device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
break;
case 0x08: // XINPUT_DEVSUBTYPE_DRUM_KIT
device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
break;
case 0x13: // XINPUT_DEVSUBTYPE_ARCADE_PAD
device->joystick_type = SDL_JOYSTICK_TYPE_ARCADE_PAD;
break;
}
device->guid.data[15] = ctx->capabilities.subType;
const Uint8 capabilities_packet[] = { 0x00, 0x00, 0x02, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
if (SDL_hid_write(device->dev, capabilities_packet, sizeof(capabilities_packet)) != sizeof(capabilities_packet)) {
SDL_SetError("Couldn't write capabilities_packet packet");
}
} else if (size == 29 && data[0] == 0x00 && data[1] == 0x00 && data[2] == 0x00 && data[3] == 0x13) {
#ifdef DEBUG_JOYSTICK
SDL_Log("Battery status: %d", data[4]);
@@ -335,6 +387,32 @@ static bool HIDAPI_DriverXbox360W_UpdateDevice(SDL_HIDAPI_Device *device)
if (joystick) {
UpdatePowerLevel(joystick, data[4]);
}
} else if (data[0] == 0x00 && data[1] == 0x05 && data[5] == 0x12) {
ctx->capabilities.gamepad.wButtons = LOAD16(data[6], data[7]);
ctx->capabilities.gamepad.bLeftTrigger = data[8];
ctx->capabilities.gamepad.bRightTrigger = data[9];
ctx->capabilities.gamepad.sThumbLX = LOAD16(data[10], data[11]);
ctx->capabilities.gamepad.sThumbLY = LOAD16(data[12], data[13]);
ctx->capabilities.gamepad.sThumbRX = LOAD16(data[14], data[15]);
ctx->capabilities.gamepad.sThumbRY = LOAD16(data[16], data[17]);
ctx->capabilities.flags |= data[20];
ctx->capabilities.vibration.wLeftMotorSpeed = data[18] << 8;
ctx->capabilities.vibration.wRightMotorSpeed = data[19] << 8;
#ifdef DEBUG_XBOX_PROTOCOL
SDL_Log("Xbox 360 capabilities:");
SDL_Log(" type: %02x", ctx->capabilities.type);
SDL_Log(" subType: %02x", ctx->capabilities.subType);
SDL_Log(" flags: %02x", ctx->capabilities.flags);
SDL_Log(" wButtons: %02x", ctx->capabilities.gamepad.wButtons);
SDL_Log(" bLeftTrigger: %02x", ctx->capabilities.gamepad.bLeftTrigger);
SDL_Log(" bRightTrigger: %02x", ctx->capabilities.gamepad.bRightTrigger);
SDL_Log(" sThumbLX: %02x", ctx->capabilities.gamepad.sThumbLX);
SDL_Log(" sThumbLY: %02x", ctx->capabilities.gamepad.sThumbLY);
SDL_Log(" sThumbRX: %02x", ctx->capabilities.gamepad.sThumbRX);
SDL_Log(" sThumbRY: %02x", ctx->capabilities.gamepad.sThumbRY);
SDL_Log(" wLeftMotorSpeed: %02x", ctx->capabilities.vibration.wLeftMotorSpeed);
SDL_Log(" wRightMotorSpeed: %02x", ctx->capabilities.vibration.wRightMotorSpeed);
#endif
} else if (size == 29 && data[0] == 0x00 && (data[1] & 0x01) == 0x01) {
if (joystick) {
HIDAPI_DriverXbox360W_HandleStatePacket(joystick, device->dev, ctx, data + 4, size - 4);