Added support for parsing the Xbox report descriptor

This gives us more robust handling of Bluetooth Xbox controllers which may vary the report format between firmware versions.

Firmware versions tested:
Xbox One S: 3.1.1, 4.8.1923, 5.13.3143
Xbox One S/X: 5.11.3118, 5.23.6
Xbox Elite Series 2: 5.22.16, 5.23.6

Fixes https://github.com/libsdl-org/SDL/issues/14597
This commit is contained in:
Sam Lantinga
2025-12-10 09:56:30 -08:00
parent ef416e84a1
commit 450a2cb5e4
9 changed files with 1002 additions and 32 deletions

View File

@@ -469,6 +469,7 @@
<ClInclude Include="..\..\src\joystick\controller_type.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_report_descriptor.h" />
<ClInclude Include="..\..\src\joystick\SDL_gamepad_c.h" />
<ClInclude Include="..\..\src\joystick\SDL_gamepad_db.h" />
<ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
@@ -744,6 +745,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_zuiki.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_report_descriptor.c" />
<ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
<ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
<ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />

View File

@@ -88,6 +88,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_zuiki.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_report_descriptor.c" />
<ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
<ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
<ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />
@@ -372,6 +373,7 @@
<ClInclude Include="..\..\src\joystick\controller_type.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_report_descriptor.h" />
<ClInclude Include="..\..\src\joystick\SDL_gamepad_c.h" />
<ClInclude Include="..\..\src\joystick\SDL_gamepad_db.h" />
<ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />

View File

@@ -383,6 +383,7 @@
<ClInclude Include="..\..\src\joystick\controller_type.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapijoystick_c.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.h" />
<ClInclude Include="..\..\src\joystick\hidapi\SDL_report_descriptor.h" />
<ClInclude Include="..\..\src\joystick\SDL_gamepad_c.h" />
<ClInclude Include="..\..\src\joystick\SDL_gamepad_db.h" />
<ClInclude Include="..\..\src\joystick\SDL_joystick_c.h" />
@@ -635,6 +636,7 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_xboxone.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_lg4ff.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapi_zuiki.c" />
<ClCompile Include="..\..\src\joystick\hidapi\SDL_report_descriptor.c" />
<ClCompile Include="..\..\src\joystick\SDL_gamepad.c" />
<ClCompile Include="..\..\src\joystick\SDL_joystick.c" />
<ClCompile Include="..\..\src\joystick\SDL_steam_virtual_gamepad.c" />

View File

@@ -675,6 +675,9 @@
<ClInclude Include="..\..\src\joystick\hidapi\SDL_hidapi_rumble.h">
<Filter>joystick\hidapi</Filter>
</ClInclude>
<ClInclude Include="..\..\src\joystick\hidapi\SDL_report_descriptor.h">
<Filter>joystick\hidapi</Filter>
</ClInclude>
<ClInclude Include="..\..\src\joystick\windows\SDL_dinputjoystick_c.h">
<Filter>joystick\windows</Filter>
</ClInclude>
@@ -1305,6 +1308,9 @@
<ClCompile Include="..\..\src\joystick\hidapi\SDL_hidapijoystick.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
<ClCompile Include="..\..\src\joystick\hidapi\SDL_report_descriptor.c">
<Filter>joystick\hidapi</Filter>
</ClCompile>
<ClCompile Include="..\..\src\joystick\windows\SDL_dinputjoystick.c">
<Filter>joystick\windows</Filter>
</ClCompile>

View File

@@ -3,7 +3,7 @@
archiveVersion = 1;
classes = {
};
objectVersion = 54;
objectVersion = 55;
objects = {
/* Begin PBXAggregateTarget section */
@@ -414,8 +414,8 @@
F386F6F02884663E001840AA /* SDL_utils_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F386F6E52884663E001840AA /* SDL_utils_c.h */; };
F386F6F92884663E001840AA /* SDL_utils.c in Sources */ = {isa = PBXBuildFile; fileRef = F386F6E62884663E001840AA /* SDL_utils.c */; };
F388C95528B5F6F700661ECF /* SDL_hidapi_ps3.c in Sources */ = {isa = PBXBuildFile; fileRef = F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */; };
F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */ = {isa = PBXBuildFile; fileRef = F39344CD2E99771B0056986F /* SDL_dlopennote.h */; settings = {ATTRIBUTES = (Public, ); }; };
F38C72492CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c in Sources */ = {isa = PBXBuildFile; fileRef = F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */; };
F39344CE2E99771B0056986F /* SDL_dlopennote.h in Headers */ = {isa = PBXBuildFile; fileRef = F39344CD2E99771B0056986F /* SDL_dlopennote.h */; settings = {ATTRIBUTES = (Public, ); }; };
F395BF6525633B2400942BFF /* SDL_crc32.c in Sources */ = {isa = PBXBuildFile; fileRef = F395BF6425633B2400942BFF /* SDL_crc32.c */; };
F395C1932569C68F00942BFF /* SDL_iokitjoystick_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */; };
F395C19C2569C68F00942BFF /* SDL_iokitjoystick.c in Sources */ = {isa = PBXBuildFile; fileRef = F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */; };
@@ -530,6 +530,10 @@
F3DDCC5B2AFD42B600B0842B /* SDL_video_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC522AFD42B600B0842B /* SDL_video_c.h */; };
F3DDCC5D2AFD42B600B0842B /* SDL_rect_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */; };
F3E5A6EB2AD5E0E600293D83 /* SDL_properties.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */; };
F3E6C3932EE9F20000A6B39E /* SDL_report_descriptor.c in Sources */ = {isa = PBXBuildFile; fileRef = F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */; };
F3E6C3942EE9F20000A6B39E /* SDL_hidapi_flydigi.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */; };
F3E6C3952EE9F20000A6B39E /* SDL_hidapi_sinput.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */; };
F3E6C3962EE9F20000A6B39E /* SDL_report_descriptor.h in Headers */ = {isa = PBXBuildFile; fileRef = F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */; };
F3EFA5ED2D5AB97300BCF22F /* SDL_stb_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */; };
F3EFA5EE2D5AB97300BCF22F /* stb_image.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EC2D5AB97300BCF22F /* stb_image.h */; };
F3EFA5EF2D5AB97300BCF22F /* SDL_surface_c.h in Headers */ = {isa = PBXBuildFile; fileRef = F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */; };
@@ -998,8 +1002,8 @@
F386F6E52884663E001840AA /* SDL_utils_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_utils_c.h; sourceTree = "<group>"; };
F386F6E62884663E001840AA /* SDL_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_utils.c; sourceTree = "<group>"; };
F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps3.c; sourceTree = "<group>"; };
F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = "<group>"; };
F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_triton.c; sourceTree = "<group>"; };
F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = "<group>"; };
F395BF6425633B2400942BFF /* SDL_crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_crc32.c; sourceTree = "<group>"; };
F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_iokitjoystick_c.h; sourceTree = "<group>"; };
F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_iokitjoystick.c; sourceTree = "<group>"; };
@@ -1113,6 +1117,10 @@
F3DDCC522AFD42B600B0842B /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = "<group>"; };
F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_impl.h; sourceTree = "<group>"; };
F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_properties.c; sourceTree = "<group>"; };
F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_flydigi.h; sourceTree = "<group>"; };
F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_sinput.h; sourceTree = "<group>"; };
F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_report_descriptor.h; sourceTree = "<group>"; };
F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_report_descriptor.c; sourceTree = "<group>"; };
F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_stb.c; sourceTree = "<group>"; };
F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_stb_c.h; sourceTree = "<group>"; };
F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_surface_c.h; sourceTree = "<group>"; };
@@ -1948,6 +1956,7 @@
children = (
F3395BA72D9A5971007246C8 /* SDL_hidapi_8bitdo.c */,
F32305FE28939F6400E66D30 /* SDL_hidapi_combined.c */,
F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */,
F3395BA72D9A5971007246C9 /* SDL_hidapi_flydigi.c */,
A7D8A7C923E2513E00DCD162 /* SDL_hidapi_gamecube.c */,
F3B6B8092DC3EA54004954FD /* SDL_hidapi_gip.c */,
@@ -1960,6 +1969,7 @@
A75FDBC323EA380300529352 /* SDL_hidapi_rumble.h */,
A75FDBC423EA380300529352 /* SDL_hidapi_rumble.c */,
9846B07B287A9020000C35C8 /* SDL_hidapi_shield.c */,
F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */,
02D6A1C128A84B8F00A7F001 /* SDL_hidapi_sinput.c */,
F3984CCF25BCC92800374F43 /* SDL_hidapi_stadia.c */,
A75FDAAC23E2795C00529352 /* SDL_hidapi_steam.c */,
@@ -1975,6 +1985,8 @@
63124A412E5C357500A53610 /* SDL_hidapi_zuiki.c */,
A7D8A7C423E2513E00DCD162 /* SDL_hidapijoystick.c */,
A7D8A7C723E2513E00DCD162 /* SDL_hidapijoystick_c.h */,
F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */,
F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */,
);
path = hidapi;
sourceTree = "<group>";
@@ -2554,6 +2566,9 @@
F3D46ADB2D20625800D9CBDF /* SDL_pen.h in Headers */,
F3D46ADC2D20625800D9CBDF /* SDL_render.h in Headers */,
F3D46ADD2D20625800D9CBDF /* SDL_assert.h in Headers */,
F3E6C3942EE9F20000A6B39E /* SDL_hidapi_flydigi.h in Headers */,
F3E6C3952EE9F20000A6B39E /* SDL_hidapi_sinput.h in Headers */,
F3E6C3962EE9F20000A6B39E /* SDL_report_descriptor.h in Headers */,
F3D46ADE2D20625800D9CBDF /* SDL_atomic.h in Headers */,
F3D46ADF2D20625800D9CBDF /* SDL_begin_code.h in Headers */,
F3D46AE02D20625800D9CBDF /* SDL_log.h in Headers */,
@@ -2923,6 +2938,7 @@
F3C1BD752D1F1A3000846529 /* SDL_tray_utils.c in Sources */,
F382071D284F362F004DD584 /* SDL_guid.c in Sources */,
A7D8BB8D23E2514500DCD162 /* SDL_touch.c in Sources */,
F3E6C3932EE9F20000A6B39E /* SDL_report_descriptor.c in Sources */,
F31A92D228D4CB39003BFD6A /* SDL_offscreenopengles.c in Sources */,
A1626A3E2617006A003F1973 /* SDL_triangle.c in Sources */,
A7D8B3F223E2514300DCD162 /* SDL_thread.c in Sources */,

View File

@@ -26,6 +26,7 @@
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
#include "SDL_hidapi_rumble.h"
#include "SDL_report_descriptor.h"
#ifdef SDL_JOYSTICK_HIDAPI_XBOXONE
@@ -33,7 +34,9 @@
// #define DEBUG_JOYSTICK
// Define this if you want to log all packets from the controller
// #define DEBUG_XBOX_PROTOCOL
#if 0
#define DEBUG_XBOX_PROTOCOL
#endif
#if defined(SDL_PLATFORM_WIN32) || defined(SDL_PLATFORM_WINGDK)
#define XBOX_ONE_DRIVER_ACTIVE 1
@@ -134,6 +137,8 @@ typedef struct
bool has_unmapped_state;
bool has_trigger_rumble;
bool has_share_button;
bool has_separate_back_button;
bool has_separate_guide_button;
Uint8 last_paddle_state;
Uint8 low_frequency_rumble;
Uint8 high_frequency_rumble;
@@ -142,6 +147,7 @@ typedef struct
SDL_XboxOneRumbleState rumble_state;
Uint64 rumble_time;
bool rumble_pending;
SDL_ReportDescriptor *descriptor;
Uint8 last_state[USB_PACKET_LENGTH];
Uint8 *chunk_buffer;
Uint32 chunk_length;
@@ -375,6 +381,32 @@ static bool HIDAPI_DriverXboxOne_InitDevice(SDL_HIDAPI_Device *device)
device->context = ctx;
Uint8 descriptor[1024];
int descriptor_len = SDL_hid_get_report_descriptor(device->dev, descriptor, sizeof(descriptor));
if (descriptor_len > 0) {
HIDAPI_DumpPacket("Xbox One report descriptor: size = %d", descriptor, descriptor_len);
ctx->descriptor = SDL_ParseReportDescriptor(descriptor, descriptor_len);
if (ctx->descriptor) {
if (!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Z) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RZ) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_BRAKE) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_ACCELERATOR) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_BUTTON, 1) ||
!SDL_DescriptorHasUsage(ctx->descriptor, USB_USAGEPAGE_BUTTON, 15)) {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor missing expected usages, ignoring");
SDL_DestroyDescriptor(ctx->descriptor);
ctx->descriptor = NULL;
}
} else {
SDL_LogWarn(SDL_LOG_CATEGORY_INPUT, "Couldn't parse Xbox report descriptor: %s", SDL_GetError());
}
} else {
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "Xbox report descriptor not available");
}
ctx->vendor_id = device->vendor_id;
ctx->product_id = device->product_id;
ctx->start_time = SDL_GetTicks();
@@ -583,6 +615,260 @@ static bool HIDAPI_DriverXboxOne_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *de
return SDL_Unsupported();
}
static void HIDAPI_DriverXboxOne_HandleBatteryState(SDL_Joystick *joystick, unsigned int flags)
{
bool on_usb = (((flags & 0x0C) >> 2) == 0);
SDL_PowerState state;
int percent = 0;
// Mapped percentage value from:
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate
switch (flags & 0x03) {
case 0:
percent = 10;
break;
case 1:
percent = 40;
break;
case 2:
percent = 70;
break;
case 3:
percent = 100;
break;
}
if (on_usb) {
state = SDL_POWERSTATE_CHARGING;
} else {
state = SDL_POWERSTATE_ON_BATTERY;
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
}
static bool HIDAPI_DriverXboxOne_HandleDescriptorReport(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, Uint8 *data, int size)
{
const SDL_ReportDescriptor *descriptor = ctx->descriptor;
Uint64 timestamp = SDL_GetTicksNS();
// Skip the report ID
const Uint8 report_id = *data;
++data;
--size;
for (int i = 0; i < descriptor->field_count; ++i) {
DescriptorInputField *field = &descriptor->fields[i];
if (field->report_id != report_id) {
continue;
}
unsigned int value;
if (!SDL_ReadReportData(data, size, field->bit_offset, field->bit_size, &value)) {
continue;
}
switch (field->usage) {
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_X):
{
Sint16 axis = (Sint16)((int)value - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Y):
{
Sint16 axis = (Sint16)((int)value - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_Z):
{
Sint16 axis = (Sint16)((int)value - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_RZ):
{
Sint16 axis = (Sint16)((int)value - 0x8000);
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_BRAKE):
{
Sint16 axis = (Sint16)(((int)value * 64) - 32768);
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, axis);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_SIMULATION, USB_USAGE_SIMULATION_ACCELERATOR):
{
Sint16 axis = (Sint16)(((int)value * 64) - 32768);
if (axis == 32704) {
axis = 32767;
}
SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, axis);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_GENERIC_DESKTOP, USB_USAGE_GENERIC_HAT):
{
Uint8 hat;
switch (value) {
case 1:
hat = SDL_HAT_UP;
break;
case 2:
hat = SDL_HAT_RIGHTUP;
break;
case 3:
hat = SDL_HAT_RIGHT;
break;
case 4:
hat = SDL_HAT_RIGHTDOWN;
break;
case 5:
hat = SDL_HAT_DOWN;
break;
case 6:
hat = SDL_HAT_LEFTDOWN;
break;
case 7:
hat = SDL_HAT_LEFT;
break;
case 8:
hat = SDL_HAT_LEFTUP;
break;
default:
hat = SDL_HAT_CENTERED;
break;
}
SDL_SendJoystickHat(timestamp, joystick, 0, hat);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 2):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 3):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 4):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 5):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 6):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 7):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 8):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 9):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 10):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 11):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 12):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 13):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 14):
case MAKE_USAGE(USB_USAGEPAGE_BUTTON, 15):
{
static const SDL_GamepadButton button_map[] = {
// 0x0001
SDL_GAMEPAD_BUTTON_SOUTH,
// 0x0002
SDL_GAMEPAD_BUTTON_EAST,
// 0x0004
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0008
SDL_GAMEPAD_BUTTON_WEST,
// 0x0010
SDL_GAMEPAD_BUTTON_NORTH,
// 0x0020
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0040
SDL_GAMEPAD_BUTTON_LEFT_SHOULDER,
// 0x0080
SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER,
// 0x0100
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0200
SDL_GAMEPAD_BUTTON_INVALID,
// 0x0400
SDL_GAMEPAD_BUTTON_BACK,
// 0x0800
SDL_GAMEPAD_BUTTON_START,
// 0x1000
SDL_GAMEPAD_BUTTON_GUIDE,
// 0x2000
SDL_GAMEPAD_BUTTON_LEFT_STICK,
// 0x4000
SDL_GAMEPAD_BUTTON_RIGHT_STICK,
};
int button_index = (field->usage - MAKE_USAGE(USB_USAGEPAGE_BUTTON, 1));
SDL_GamepadButton button = button_map[button_index];
if (button == SDL_GAMEPAD_BUTTON_INVALID) {
break;
}
if (button == SDL_GAMEPAD_BUTTON_BACK && ctx->has_separate_back_button) {
break;
}
if (button == SDL_GAMEPAD_BUTTON_GUIDE && ctx->has_separate_guide_button) {
break;
}
bool pressed = (value != 0);
SDL_SendJoystickButton(timestamp, joystick, button, pressed);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_BACK):
{
bool pressed = (value != 0);
if (pressed) {
ctx->has_separate_back_button = true;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, pressed);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_AC_HOME):
{
bool pressed = (value != 0);
if (pressed) {
ctx->has_separate_guide_button = true;
}
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, pressed);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_RECORD):
{
if (ctx->has_share_button) {
bool pressed = (value != 0);
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON, pressed);
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ORDER_MOVIE):
{
// This value is the currently selected profile
ctx->has_unmapped_state = (value == 0);
break;
}
case MAKE_USAGE(USB_USAGEPAGE_CONSUMER, USB_USAGE_CONSUMER_ASSIGN_SELECTION):
{
if (ctx->has_paddles) {
if (!ctx->has_unmapped_state) {
value = 0;
}
Uint8 button = (Uint8)(SDL_GAMEPAD_BUTTON_XBOX_SHARE_BUTTON + ctx->has_share_button); // Next available button
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x1) != 0));
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x2) != 0));
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x4) != 0));
SDL_SendJoystickButton(timestamp, joystick, button++, ((value & 0x8) != 0));
}
break;
}
case MAKE_USAGE(USB_USAGEPAGE_DEVICE_CONTROLS, USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH):
{
HIDAPI_DriverXboxOne_HandleBatteryState(joystick, value);
break;
}
default:
break;
}
}
return true;
}
/*
* The Xbox One Elite controller with 5.13+ firmware sends the unmapped state in a separate packet.
* We can use this to send the paddle state when they aren't mapped
@@ -1066,33 +1352,7 @@ static void HIDAPI_DriverXboxOneBluetooth_HandleGuidePacket(SDL_Joystick *joysti
static void HIDAPI_DriverXboxOneBluetooth_HandleBatteryPacket(SDL_Joystick *joystick, SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
{
Uint8 flags = data[1];
bool on_usb = (((flags & 0x0C) >> 2) == 0);
SDL_PowerState state;
int percent = 0;
// Mapped percentage value from:
// https://learn.microsoft.com/en-us/gaming/gdk/_content/gc/reference/input/gameinput/interfaces/igameinputdevice/methods/igameinputdevice_getbatterystate
switch (flags & 0x03) {
case 0:
percent = 10;
break;
case 1:
percent = 40;
break;
case 2:
percent = 70;
break;
case 3:
percent = 100;
break;
}
if (on_usb) {
state = SDL_POWERSTATE_CHARGING;
} else {
state = SDL_POWERSTATE_ON_BATTERY;
}
SDL_SendJoystickPowerInfo(joystick, state, percent);
HIDAPI_DriverXboxOne_HandleBatteryState(joystick, data[1]);
}
static void HIDAPI_DriverXboxOne_HandleSerialIDPacket(SDL_DriverXboxOne_Context *ctx, const Uint8 *data, int size)
@@ -1588,7 +1848,12 @@ static bool HIDAPI_DriverXboxOne_UpdateDevice(SDL_HIDAPI_Device *device)
#ifdef DEBUG_XBOX_PROTOCOL
HIDAPI_DumpPacket("Xbox One packet: size = %d", data, size);
#endif
if (device->is_bluetooth) {
if (ctx->descriptor) {
if (!joystick) {
break;
}
HIDAPI_DriverXboxOne_HandleDescriptorReport(joystick, ctx, data, size);
} else if (device->is_bluetooth) {
switch (data[0]) {
case 0x01:
if (!joystick) {
@@ -1647,6 +1912,8 @@ static void HIDAPI_DriverXboxOne_FreeDevice(SDL_HIDAPI_Device *device)
{
SDL_DriverXboxOne_Context *ctx = (SDL_DriverXboxOne_Context *)device->context;
SDL_DestroyDescriptor(ctx->descriptor);
HIDAPI_GIP_DestroyChunkBuffer(ctx);
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,40 @@
/*
Simple DirectMedia Layer
Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
*/
#include "SDL_internal.h"
typedef struct
{
Uint8 report_id;
Uint32 usage;
int bit_offset;
int bit_size;
} DescriptorInputField;
typedef struct
{
int field_count;
DescriptorInputField *fields;
} SDL_ReportDescriptor;
extern SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size);
extern bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage);
extern void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor);
extern bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, unsigned int *value);

View File

@@ -182,7 +182,10 @@
// USB usage pages
#define USB_USAGEPAGE_GENERIC_DESKTOP 0x0001
#define USB_USAGEPAGE_SIMULATION 0x0002
#define USB_USAGEPAGE_DEVICE_CONTROLS 0x0006
#define USB_USAGEPAGE_BUTTON 0x0009
#define USB_USAGEPAGE_CONSUMER 0x000C
#define USB_USAGEPAGE_VENDOR_FLYDIGI 0xFFA0
// USB usages for USAGE_PAGE_GENERIC_DESKTOP
@@ -204,6 +207,22 @@
#define USB_USAGE_GENERIC_WHEEL 0x0038
#define USB_USAGE_GENERIC_HAT 0x0039
// USB usages for USB_USAGEPAGE_SIMULATION
#define USB_USAGE_SIMULATION_ACCELERATOR 0x00C4
#define USB_USAGE_SIMULATION_BRAKE 0x00C5
// USB usages for USB_USAGEPAGE_DEVICE_CONTROLS
#define USB_USAGE_DEVICE_CONTROLS_BATTERY_STRENGTH 0x0020
// USB usages for USB_USAGEPAGE_CONSUMER
#define USB_USAGE_CONSUMER_ASSIGN_SELECTION 0x0081
#define USB_USAGE_CONSUMER_ORDER_MOVIE 0x0085
#define USB_USAGE_CONSUMER_RECORD 0x00B2
#define USB_USAGE_CONSUMER_AC_HOME 0x0223
#define USB_USAGE_CONSUMER_AC_BACK 0x0224
#define MAKE_USAGE(PAGE, USAGE) (((Uint32)PAGE) << 16 | USAGE)
/* Bluetooth SIG assigned Company Identifiers
https://www.bluetooth.com/specifications/assigned-numbers/company-identifiers/ */
#define BLUETOOTH_VENDOR_AMAZON 0x0171