Add support for GIP instruments via hidapi, and drums via gameinput (#16009)
Build (All) / Create test plan (push) Canceled after 0s
Build (All) / level1 (push) Canceled after 0s
Build (All) / level2 (push) Canceled after 0s

This commit is contained in:
Sanjay Govind
2026-08-02 16:27:46 -07:00
committed by GitHub
parent 3b8ecd32bf
commit 7010faef34
6 changed files with 219 additions and 6 deletions
@@ -232,6 +232,7 @@ public class HIDDeviceManager {
final int XB360W_IFACE_PROTOCOL = 129; // Wireless
final int[] SUPPORTED_VENDORS = {
0x0079, // GPD Win 2
0x0351, // CRKD
0x044f, // Thrustmaster
0x045e, // Microsoft
0x046d, // Logitech
@@ -243,6 +244,7 @@ public class HIDDeviceManager {
0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon
0x1209, // Generic
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
@@ -257,7 +259,9 @@ public class HIDDeviceManager {
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x3537, // GameSir
0x3651, // CRKD
0x37d7, // Flydigi
0x3958, // Red Octane Games
0x9886, // ASTRO Gaming
};
@@ -279,6 +283,7 @@ public class HIDDeviceManager {
final int XB1_IFACE_SUBCLASS = 71;
final int XB1_IFACE_PROTOCOL = 208;
final int[] SUPPORTED_VENDORS = {
0x0351, // CRKD
0x03f0, // HP
0x044f, // Thrustmaster
0x045e, // Microsoft
@@ -287,6 +292,7 @@ public class HIDDeviceManager {
0x0e6f, // PDP
0x0f0d, // Hori
0x10f5, // Turtle Beach
0x1209, // Generic
0x1532, // Razer Wildcat
0x20d6, // PowerA
0x24c6, // PowerA
@@ -296,7 +302,9 @@ public class HIDDeviceManager {
0x2e95, // SCUF
0x3285, // Nacon
0x3537, // GameSir
0x3651, // CRKD
0x366c, // ByoWave
0x3958, // Red Octane Games
};
if (usbInterface.getId() == 0 &&
+2 -1
View File
@@ -2291,7 +2291,8 @@ extern "C" {
* - "0": Assume this is a generic controller.
* - "1": Reset the controller to get metadata.
*
* By default the controller is not reset.
* By default the controller is reset. This is so we can properly detect
* the controller type.
*
* This hint should be set before initializing joysticks and gamepads.
*
+8
View File
@@ -945,6 +945,7 @@ static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_de
static const int xb360w_iface_protocol = 129; /* Wireless */
static const int supported_vendors[] = {
0x0079, /* GPD Win 2 */
0x0351, /* CRKD */
0x044f, /* Thrustmaster */
0x045e, /* Microsoft */
0x046d, /* Logitech */
@@ -956,6 +957,7 @@ static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_de
0x0f0d, /* Hori */
0x1038, /* SteelSeries */
0x11c9, /* Nacon */
0x1209, /* Generic */
0x12ab, /* Unknown */
0x1430, /* RedOctane */
0x146b, /* BigBen */
@@ -970,7 +972,9 @@ static int is_xbox360(unsigned short vendor_id, const struct libusb_interface_de
0x2c22, /* Qanba */
0x2dc8, /* 8BitDo */
0x3537, /* GameSir */
0x3651, /* CRKD */
0x37d7, /* Flydigi */
0x3958, /* Red Octane Games */
0x9886, /* ASTRO Gaming */
};
@@ -993,6 +997,7 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de
static const int xb1_iface_subclass = 71;
static const int xb1_iface_protocol = 208;
static const int supported_vendors[] = {
0x0351, /* CRKD */
0x03f0, /* HP */
0x044f, /* Thrustmaster */
0x045e, /* Microsoft */
@@ -1001,6 +1006,7 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de
0x0e6f, /* PDP */
0x0f0d, /* Hori */
0x10f5, /* Turtle Beach */
0x1209, /* Generic */
0x1532, /* Razer Wildcat */
0x20d6, /* PowerA */
0x24c6, /* PowerA */
@@ -1010,7 +1016,9 @@ static int is_xboxone(unsigned short vendor_id, const struct libusb_interface_de
0x2e95, /* SCUF */
0x3285, /* Nacon */
0x3537, /* GameSir */
0x3651, /* CRKD */
0x366c, /* ByoWave */
0x3958, /* Red Octane Games */
};
if (intf_desc->bInterfaceNumber == 0 &&
+42 -1
View File
@@ -747,7 +747,46 @@ static bool GAMEINPUT_JoystickSetSensorsEnabled(SDL_Joystick *joystick, bool ena
joystick->hwdata->report_sensors = enabled;
return true;
}
static void GAMEINPUT_DrumUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp)
{
#if GAMEINPUT_API_VERSION >= 3
IGameInputRawDeviceReport *rawState;
if (reading->GetRawReport(&rawState)) {
uint8_t rawData[40];
SDL_memset(rawData, 0, sizeof(rawData));
size_t len = rawState->GetRawData(sizeof(rawData), rawData);
Uint8 hat = 0;
uint16_t buttons = rawData[0] | rawData[1] << 8;
if (len >= 6) {
if ((buttons & 0x0100) || (rawData[4] & 0xf0)) {
hat |= SDL_HAT_UP;
}
if ((buttons & 0x0200) || (rawData[4] & 0x0f)) {
hat |= SDL_HAT_DOWN;
}
if (buttons & 0x0400) {
hat |= SDL_HAT_LEFT;
}
if (buttons & 0x0800) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
// The rest of the instruments are mapped to PS3 style inputs
// The PS3 used flags for dictating if a pad or a cymbal was hit, so we emulate that here
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (buttons & 0x0008));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (buttons & 0x0004));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (buttons & 0x1000));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (buttons & 0x2000));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (rawData[3] & 0x0f) || (rawData[5] & 0xf0) || (buttons & 0x0010));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (rawData[2] & 0xf0) || (buttons & 0x0020));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (rawData[3] & 0xf0) || (rawData[4] & 0x0f) || (buttons & 0x0040));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (rawData[2] & 0x0f) || (rawData[4] & 0xf0) || (buttons & 0x0080));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, rawData[2] || rawData[3]);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, rawData[4] || rawData[5]);
}
}
#endif // GAMEINPUT_API_VERSION >= 3
}
static void GAMEINPUT_GuitarUpdate(SDL_Joystick *joystick, IGameInputReading *reading, Uint64 timestamp)
{
#if GAMEINPUT_API_VERSION >= 3
@@ -946,6 +985,8 @@ static void GAMEINPUT_JoystickUpdate(SDL_Joystick *joystick)
timestamp = SDL_US_TO_NS(reading->GetTimestamp() + g_GameInputTimestampOffset);
if (internal_device->raw_type == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_GUITAR) {
GAMEINPUT_GuitarUpdate(joystick, reading, timestamp);
} else if (internal_device->raw_type == SDL_GAMEINPUT_RAWTYPE_ROCK_BAND_DRUM_KIT) {
GAMEINPUT_DrumUpdate(joystick, reading, timestamp);
} else if (GAMEINPUT_InternalIsGamepad(info)) {
GAMEINPUT_GamepadUpdate(joystick, reading, timestamp);
} else {
+151 -4
View File
@@ -261,6 +261,9 @@ typedef enum
GIP_TYPE_NAVIGATION_CONTROLLER = 4,
GIP_TYPE_CHATPAD = 5,
GIP_TYPE_HEADSET = 6,
GIP_TYPE_GUITAR = 7,
GIP_TYPE_DRUM_KIT = 8,
GIP_TYPE_LIVE_GUITAR = 9,
} GIP_AttachmentType;
typedef enum
@@ -304,6 +307,11 @@ MAKE_GUID(GUID_IEliteButtons, 0x37d19ff7, 0xb5c6, 0x49d1, 0xa7, 0x5e, 0x03, 0xb2
MAKE_GUID(GUID_IGamepad, 0x082e402c, 0x07df, 0x45e1, 0xa5, 0xab, 0xa3, 0x12, 0x7a, 0xf1, 0x97, 0xb5);
MAKE_GUID(GUID_NavigationController, 0xb8f31fe7, 0x7386, 0x40e9, 0xa9, 0xf8, 0x2f, 0x21, 0x26, 0x3a, 0xcf, 0xb7);
MAKE_GUID(GUID_Wheel, 0x646979cf, 0x6b71, 0x4e96, 0x8d, 0xf9, 0x59, 0xe3, 0x98, 0xd7, 0x42, 0x0c);
MAKE_GUID(GUID_PDP_Guitar, 0x1a266af6, 0x3a46, 0x45e3, 0xb9, 0xb6, 0x0f, 0x2c, 0x0b, 0x2c, 0x1e, 0xbe);
MAKE_GUID(GUID_Madcatz_Guitar, 0x0d2ae438, 0x7f7d, 0x4933, 0x86, 0x93, 0x30, 0xfc, 0x55, 0x01, 0x8e, 0x77);
MAKE_GUID(GUID_PDP_DrumKit, 0xa503f9b0, 0x955e, 0x47c4, 0xa2, 0xed, 0xb1, 0x33, 0x6f, 0xa7, 0x70, 0x3e);
MAKE_GUID(GUID_Madcatz_DrumKit, 0x06182893, 0xCCE0, 0x4B85, 0x92, 0x71, 0x0A, 0x10, 0xDB, 0xAB, 0x7E, 0x07);
MAKE_GUID(GUID_GuitarHero_Live_Guitar, 0xfd12fdd9, 0x8e73, 0x47c7, 0xa2, 0x31, 0x96, 0x26, 0x8c, 0x38, 0x00, 0x9a);
/*
* The following GUIDs are observed, but the exact meanings aren't known, so
@@ -1258,6 +1266,9 @@ static bool GIP_EnsureMetadata(GIP_Attachment *attachment)
attachment->metadata_retries = 0;
return GIP_SendSystemMessage(attachment, GIP_CMD_METADATA, 0, NULL, 0);
} else {
if (attachment->device->reset_for_metadata) {
return true;
}
return GIP_SetMetadataDefaults(attachment);
}
default:
@@ -1592,6 +1603,31 @@ static bool GIP_HandleCommandMetadataRespose(
expected_guid = &GUID_IHeadset;
break;
}
if (SDL_strcmp(type, "Activision.Xbox.Input.GH7") == 0) {
attachment->attachment_type = GIP_TYPE_LIVE_GUITAR;
expected_guid = &GUID_GuitarHero_Live_Guitar;
break;
}
if (SDL_strcmp(type, "MadCatz.Xbox.Guitar.Stratocaster") == 0) {
attachment->attachment_type = GIP_TYPE_GUITAR;
expected_guid = &GUID_Madcatz_Guitar;
break;
}
if (SDL_strcmp(type, "PDP.Xbox.Guitar.Jaguar") == 0) {
attachment->attachment_type = GIP_TYPE_GUITAR;
expected_guid = &GUID_PDP_Guitar;
break;
}
if (SDL_strcmp(type, "MadCatz.Xbox.Drums.Glam") == 0) {
attachment->attachment_type = GIP_TYPE_DRUM_KIT;
expected_guid = &GUID_Madcatz_DrumKit;
break;
}
if (SDL_strcmp(type, "PDP.Xbox.Drums.Tablah") == 0) {
attachment->attachment_type = GIP_TYPE_DRUM_KIT;
expected_guid = &GUID_PDP_DrumKit;
break;
}
}
found_expected_guid = !expected_guid;
@@ -2027,6 +2063,99 @@ static void GIP_HandleGamepadReport(
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, ~axis);
}
static void GIP_HandleGuitarReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
Uint64 timestamp,
const Uint8 *bytes,
int num_bytes)
{
static uint16_t s_GuitarButtons[] = {
0x0010, // SDL_GAMEPAD_BUTTON_SOUTH
0x0020, // SDL_GAMEPAD_BUTTON_EAST
0x0040, // SDL_GAMEPAD_BUTTON_WEST
0x0080, // SDL_GAMEPAD_BUTTON_NORTH
0x0008, // SDL_GAMEPAD_BUTTON_BACK
0, // The guide button is not available
0x0004, // SDL_GAMEPAD_BUTTON_START
0, // right joystick click unavailable
0x4000, // SDL_GAMEPAD_BUTTON_RIGHT_STICK
0x1000, // SDL_GAMEPAD_BUTTON_LEFT_SHOULDER
0, // right shoulder unavailable
};
Uint8 btnidx = 0, hat = 0;
uint16_t buttons = bytes[0] | bytes[1] << 8;
if (num_bytes >= 10) {
for (btnidx = 0; btnidx < SDL_arraysize(s_GuitarButtons); ++btnidx) {
uint16_t button_mask = s_GuitarButtons[btnidx];
if (!button_mask) {
continue;
}
bool down = ((buttons & button_mask) != 0);
SDL_SendJoystickButton(timestamp, joystick, btnidx, down);
}
if (buttons & 0x0100) {
hat |= SDL_HAT_UP;
}
if (buttons & 0x0200) {
hat |= SDL_HAT_DOWN;
}
if (buttons & 0x0400) {
hat |= SDL_HAT_LEFT;
}
if (buttons & 0x0800) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, (bytes[3] * 257) - 32768);
// PS3 RB guitars had tilt on right shoulder
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, bytes[2] >= 0xD0);
// PS3 RB guitars send L2 when using solo buttons
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (bytes[6]) ? 32767 : -32768);
// Align pickup selector mappings with PS3 instruments
static const Sint16 effects_mappings[] = {-26880, -13568, -1792, 11008, 24576};
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, effects_mappings[bytes[4] >> 4]);
}
}
static void GIP_HandleDrumKitReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
Uint64 timestamp,
const Uint8 *bytes,
int num_bytes)
{
Uint8 hat = 0;
uint16_t buttons = bytes[0] | bytes[1] << 8;
if (num_bytes >= 6) {
if ((buttons & 0x0100) || (bytes[4] & 0xf0)) {
hat |= SDL_HAT_UP;
}
if ((buttons & 0x0200) || (bytes[4] & 0x0f)) {
hat |= SDL_HAT_DOWN;
}
if (buttons & 0x0400) {
hat |= SDL_HAT_LEFT;
}
if (buttons & 0x0800) {
hat |= SDL_HAT_RIGHT;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
// The rest of the instruments are mapped to PS3 style inputs
// The PS3 used flags for dictating if a pad or a cymbal was hit, so we emulate that here
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, (buttons & 0x0008));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, (buttons & 0x0004));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, (buttons & 0x1000));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, (buttons & 0x2000));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, (bytes[3] & 0x0f) || (bytes[5] & 0xf0) || (buttons & 0x0010));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, (bytes[2] & 0xf0) || (buttons & 0x0020));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, (bytes[3] & 0xf0) || (bytes[4] & 0x0f) || (buttons & 0x0040));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, (bytes[2] & 0x0f) || (bytes[4] & 0xf0) || (buttons & 0x0080));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, bytes[2] || bytes[3]);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, bytes[4] || bytes[5]);
}
}
static void GIP_HandleArcadeStickReport(
GIP_Attachment *attachment,
SDL_Joystick *joystick,
@@ -2149,7 +2278,7 @@ static bool GIP_HandleLLInputReport(
return true;
}
if (num_bytes < 14) {
if (num_bytes < 6) {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "GIP: Discarding too-short input report");
return false;
}
@@ -2167,6 +2296,12 @@ static bool GIP_HandleLLInputReport(
case GIP_TYPE_FLIGHT_STICK:
GIP_HandleFlightStickReport(attachment, joystick, timestamp, bytes, num_bytes);
break;
case GIP_TYPE_GUITAR:
GIP_HandleGuitarReport(attachment, joystick, timestamp, bytes, num_bytes);
break;
case GIP_TYPE_DRUM_KIT:
GIP_HandleDrumKitReport(attachment, joystick, timestamp, bytes, num_bytes);
break;
}
if (attachment->features & GIP_FEATURE_ELITE_BUTTONS) {
@@ -2243,8 +2378,8 @@ static bool GIP_HandleLLInputReport(
0);
}
}
if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 32) {
// Input reports are at a minimum 5 bytes, and the console function map is always 18 bytes
if ((attachment->features & GIP_FEATURE_CONSOLE_FUNCTION_MAP) && num_bytes >= 23) {
int function_map_offset = -1;
if (attachment->features & GIP_FEATURE_DYNAMIC_LATENCY_INPUT) {
/* The dynamic latency input bytes are after the console function map */
@@ -2254,7 +2389,7 @@ static bool GIP_HandleLLInputReport(
} else {
function_map_offset = num_bytes - 18;
}
if (function_map_offset >= 14) {
if (function_map_offset >= 5) {
if (attachment->last_input[function_map_offset] != bytes[function_map_offset]) {
SDL_SendJoystickButton(timestamp,
joystick,
@@ -2739,6 +2874,18 @@ static bool HIDAPI_DriverGIP_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic
joystick->naxes += attachment->extra_axes - 1;
}
if (attachment->attachment_type == GIP_TYPE_GUITAR) {
device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
}
if (attachment->attachment_type == GIP_TYPE_DRUM_KIT) {
device->joystick_type = SDL_JOYSTICK_TYPE_DRUM_KIT;
}
if (attachment->attachment_type == GIP_TYPE_LIVE_GUITAR) {
device->joystick_type = SDL_JOYSTICK_TYPE_GUITAR;
}
joystick->nhats = 1;
return true;
+8
View File
@@ -268,6 +268,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
static const int SUPPORTED_VENDORS[] = {
0x0079, // GPD Win 2
0x0351, // CRKD
0x044f, // Thrustmaster
0x045e, // Microsoft
0x046d, // Logitech
@@ -279,6 +280,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
0x0f0d, // Hori
0x1038, // SteelSeries
0x11c9, // Nacon
0x1209, // Generic
0x12ab, // Unknown
0x1430, // RedOctane
0x146b, // BigBen
@@ -293,7 +295,9 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
0x2c22, // Qanba
0x2dc8, // 8BitDo
0x3537, // GameSir
0x3651, // CRKD
0x37d7, // Flydigi
0x3958, // Red Octane Games
0x9886, // ASTRO Gaming
};
@@ -312,6 +316,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
interface_protocol == XBONE_IFACE_PROTOCOL) {
static const int SUPPORTED_VENDORS[] = {
0x0351, // CRKD
0x03f0, // HP
0x044f, // Thrustmaster
0x045e, // Microsoft
@@ -320,6 +325,7 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
0x0e6f, // PDP
0x0f0d, // Hori
0x10f5, // Turtle Beach
0x1209, // Generic
0x1532, // Razer
0x20d6, // PowerA
0x24c6, // PowerA
@@ -329,7 +335,9 @@ static SDL_GamepadType SDL_GetJoystickGameControllerProtocol(const char *name, U
0x2e95, // SCUF
0x3285, // Nacon
0x3537, // GameSir
0x3651, // CRKD
0x366c, // ByoWave
0x3958, // Red Octane Games
};
int i;