diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index 7e2e93a4f1..a1356ab17f 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -469,6 +469,7 @@ + @@ -744,6 +745,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index e54d76cbfa..a2f431abf1 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -88,6 +88,7 @@ + @@ -372,6 +373,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index f5a1612f4d..2937791cf7 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -383,6 +383,7 @@ + @@ -635,6 +636,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index b23275a29e..e1014918b2 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -675,6 +675,9 @@ joystick\hidapi + + joystick\hidapi + joystick\windows @@ -1305,6 +1308,9 @@ joystick\hidapi + + joystick\hidapi + joystick\windows diff --git a/Xcode/SDL/SDL.xcodeproj/project.pbxproj b/Xcode/SDL/SDL.xcodeproj/project.pbxproj index b6aca459a4..7dbf5a28bd 100644 --- a/Xcode/SDL/SDL.xcodeproj/project.pbxproj +++ b/Xcode/SDL/SDL.xcodeproj/project.pbxproj @@ -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 = ""; }; F386F6E62884663E001840AA /* SDL_utils.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_utils.c; sourceTree = ""; }; F388C95428B5F6F600661ECF /* SDL_hidapi_ps3.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_ps3.c; sourceTree = ""; }; - F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = ""; }; F38C72482CEEB1DE000B0A90 /* SDL_hidapi_steam_triton.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_hidapi_steam_triton.c; sourceTree = ""; }; + F39344CD2E99771B0056986F /* SDL_dlopennote.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_dlopennote.h; sourceTree = ""; }; F395BF6425633B2400942BFF /* SDL_crc32.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_crc32.c; sourceTree = ""; }; F395C1912569C68E00942BFF /* SDL_iokitjoystick_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_iokitjoystick_c.h; sourceTree = ""; }; F395C1922569C68E00942BFF /* SDL_iokitjoystick.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_iokitjoystick.c; sourceTree = ""; }; @@ -1113,6 +1117,10 @@ F3DDCC522AFD42B600B0842B /* SDL_video_c.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_video_c.h; sourceTree = ""; }; F3DDCC542AFD42B600B0842B /* SDL_rect_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL_rect_impl.h; sourceTree = ""; }; F3E5A6EA2AD5E0E600293D83 /* SDL_properties.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = SDL_properties.c; sourceTree = ""; }; + F3E6C38F2EE9F20000A6B39E /* SDL_hidapi_flydigi.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_flydigi.h; sourceTree = ""; }; + F3E6C3902EE9F20000A6B39E /* SDL_hidapi_sinput.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_hidapi_sinput.h; sourceTree = ""; }; + F3E6C3912EE9F20000A6B39E /* SDL_report_descriptor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_report_descriptor.h; sourceTree = ""; }; + F3E6C3922EE9F20000A6B39E /* SDL_report_descriptor.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_report_descriptor.c; sourceTree = ""; }; F3EFA5E92D5AB97300BCF22F /* SDL_stb.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = SDL_stb.c; sourceTree = ""; }; F3EFA5EA2D5AB97300BCF22F /* SDL_stb_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_stb_c.h; sourceTree = ""; }; F3EFA5EB2D5AB97300BCF22F /* SDL_surface_c.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDL_surface_c.h; sourceTree = ""; }; @@ -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 = ""; @@ -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 */, diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c index c48b9e1b8c..35e317e967 100644 --- a/src/joystick/hidapi/SDL_hidapi_xboxone.c +++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c @@ -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); } diff --git a/src/joystick/hidapi/SDL_report_descriptor.c b/src/joystick/hidapi/SDL_report_descriptor.c new file mode 100644 index 0000000000..5dbc5246ed --- /dev/null +++ b/src/joystick/hidapi/SDL_report_descriptor.c @@ -0,0 +1,616 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + 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" + +#include "SDL_report_descriptor.h" + +// This is a very simple (and non-compliant!) report descriptor parser +// used to quickly parse Xbox Bluetooth reports + +typedef enum +{ + DescriptorItemTypeMain = 0, + DescriptorItemTypeGlobal = 1, + DescriptorItemTypeLocal = 2, + DescriptorItemTypeReserved = 3, +} ItemType; + +typedef enum +{ + MainTagInput = 0x8, + MainTagOutput = 0x9, + MainTagFeature = 0xb, + MainTagCollection = 0xa, + MainTagEndCollection = 0xc, +} MainTag; + +typedef enum +{ + MainFlagConstant = 0x0001, + MainFlagVariable = 0x0002, + MainFlagRelative = 0x0004, + MainFlagWrap = 0x0008, + MainFlagNonLinear = 0x0010, + MainFlagNoPreferred = 0x0020, + MainFlagNullState = 0x0040, + MainFlagVolatile = 0x0080, + MainFlagBufferedBytes = 0x0100, +} MainFlag; + +typedef enum +{ + GlobalTagUsagePage = 0x0, + GlobalTagLogicalMinimum = 0x1, + GlobalTagLogicalMaximum = 0x2, + GlobalTagPhysicalMinimum = 0x3, + GlobalTagPhysicalMaximum = 0x4, + GlobalTagUnitExponent = 0x5, + GlobalTagUnit = 0x6, + GlobalTagReportSize = 0x7, + GlobalTagReportID = 0x8, + GlobalTagReportCount = 0x9, + GlobalTagPush = 0xa, + GlobalTagPop = 0xb, +} GlobalTag; + +typedef enum +{ + LocalTagUsage = 0x0, + LocalTagUsageMinimum = 0x1, + LocalTagUsageMaximum = 0x2, + LocalTagDesignatorIndex = 0x3, + LocalTagDesignatorMinimum = 0x4, + LocalTagDesignatorMaximum = 0x5, + LocalTagStringIndex = 0x7, + LocalTagStringMinimum = 0x8, + LocalTagStringMaximum = 0x9, + LocalTagDelimiter = 0xa, +} LocalTag; + +typedef struct +{ + unsigned int usage_page; + unsigned int report_size; + unsigned int report_count; + unsigned int report_id; +} DescriptorGlobalState; + +typedef struct +{ + unsigned int usage_minimum; + unsigned int usage_maximum; + int usage_maxcount; + int usage_count; + Uint32 *usages; +} DescriptorLocalState; + +typedef struct +{ + int collection_depth; + DescriptorGlobalState global; + DescriptorLocalState local; + int field_maxcount; + int field_count; + int field_offset; + DescriptorInputField *fields; +} DescriptorContext; + +static void DebugDescriptor(DescriptorContext *ctx, const char *fmt, ...) +{ +#ifdef DEBUG_DESCRIPTOR + va_list ap; + va_start(ap, fmt); + char *message = NULL; + SDL_vasprintf(&message, fmt, ap); + va_end(ap); + if (ctx->collection_depth > 0) { + size_t len = 4 * ctx->collection_depth + SDL_strlen(message) + 1; + char *output = (char *)SDL_malloc(len); + if (output) { + SDL_memset(output, ' ', 4 * ctx->collection_depth); + output[4 * ctx->collection_depth] = '\0'; + SDL_strlcat(output, message, len); + SDL_free(message); + message = output; + } + } + SDL_Log("%s", message); + SDL_free(message); +#endif // DEBUG_DESCRIPTOR +} + +static void DebugMainTag(DescriptorContext *ctx, const char *tag, unsigned int flags) +{ +#ifdef DEBUG_DESCRIPTOR + char message[1024] = { 0 }; + + SDL_strlcat(message, tag, sizeof(message)); + SDL_strlcat(message, "(", sizeof(message)); + if (flags & MainFlagConstant) { + SDL_strlcat(message, " Constant", sizeof(message)); + } else { + SDL_strlcat(message, " Data", sizeof(message)); + } + if (flags & MainFlagVariable) { + SDL_strlcat(message, " Variable", sizeof(message)); + } else { + SDL_strlcat(message, " Array", sizeof(message)); + } + if (flags & MainFlagRelative) { + SDL_strlcat(message, " Relative", sizeof(message)); + } else { + SDL_strlcat(message, " Absolute", sizeof(message)); + } + if (flags & MainFlagWrap) { + SDL_strlcat(message, " Wrap", sizeof(message)); + } else { + SDL_strlcat(message, " No Wrap", sizeof(message)); + } + if (flags & MainFlagNonLinear) { + SDL_strlcat(message, " Non Linear", sizeof(message)); + } else { + SDL_strlcat(message, " Linear", sizeof(message)); + } + if (flags & MainFlagNoPreferred) { + SDL_strlcat(message, " No Preferred", sizeof(message)); + } else { + SDL_strlcat(message, " Preferred State", sizeof(message)); + } + if (flags & MainFlagNullState) { + SDL_strlcat(message, " Null State", sizeof(message)); + } else { + SDL_strlcat(message, " No Null Position", sizeof(message)); + } + if (flags & MainFlagVolatile) { + SDL_strlcat(message, " Volatile", sizeof(message)); + } else { + SDL_strlcat(message, " Non Volatile", sizeof(message)); + } + if (flags & MainFlagBufferedBytes) { + SDL_strlcat(message, " Buffered Bytes", sizeof(message)); + } else { + SDL_strlcat(message, " Bit Field", sizeof(message)); + } + SDL_strlcat(message, " )", sizeof(message)); + + DebugDescriptor(ctx, "%s", message); + +#endif // DEBUG_DESCRIPTOR +} + +static unsigned int ReadValue(const Uint8 *data, int size) +{ + unsigned int value = 0; + + int shift = 0; + while (size--) { + value |= ((unsigned int)*data++) << shift; + shift += 8; + } + return value; +} + +static void ResetLocalState(DescriptorContext *ctx) +{ + ctx->local.usage_minimum = 0; + ctx->local.usage_maximum = 0; + ctx->local.usage_count = 0; +} + +static bool AddUsage(DescriptorContext *ctx, unsigned int usage) +{ + if (ctx->local.usage_count == ctx->local.usage_maxcount) { + int usage_maxcount = ctx->local.usage_maxcount + 4; + Uint32 *usages = (Uint32 *)SDL_realloc(ctx->local.usages, usage_maxcount * sizeof(*usages)); + if (!usages) { + return false; + } + ctx->local.usages = usages; + ctx->local.usage_maxcount = usage_maxcount; + } + + if (usage <= 0xFFFF) { + usage |= (ctx->global.usage_page << 16); + } + ctx->local.usages[ctx->local.usage_count++] = usage; + return true; +} + +static bool AddInputField(DescriptorContext *ctx, unsigned int usage, int bit_size) +{ + if (ctx->field_count == ctx->field_maxcount) { + int field_maxcount = ctx->field_maxcount + 4; + DescriptorInputField *fields = (DescriptorInputField *)SDL_realloc(ctx->fields, field_maxcount * sizeof(*fields)); + if (!fields) { + return false; + } + ctx->fields = fields; + ctx->field_maxcount = field_maxcount; + } + + DescriptorInputField *field = &ctx->fields[ctx->field_count++]; + field->report_id = (Uint8)ctx->global.report_id; + field->usage = usage; + field->bit_offset = ctx->field_offset; + field->bit_size = bit_size; + + DebugDescriptor(ctx, "Adding report %d field 0x%.8x size %d bits at bit offset %d", field->report_id, field->usage, field->bit_size, field->bit_offset); + return true; +} + +static bool AddInputFields(DescriptorContext *ctx) +{ + unsigned int usage = 0; + + if (ctx->global.report_count == 0 || ctx->global.report_size == 0) { + return true; + } + + if (ctx->local.usage_count == 0 && + ctx->local.usage_minimum > 0 && + ctx->local.usage_maximum >= ctx->local.usage_minimum) { + for (usage = ctx->local.usage_minimum; usage <= ctx->local.usage_maximum; ++usage) { + if (!AddUsage(ctx, usage)) { + return false; + } + } + } + + int usage_index = 0; + for (unsigned int i = 0; i < ctx->global.report_count; ++i) { + if (usage_index < ctx->local.usage_count) { + usage = ctx->local.usages[usage_index]; + if (usage_index < (ctx->local.usage_count - 1)) { + ++usage_index; + } + } + + int size = (int)ctx->global.report_size; + if (usage > 0) { + if (!AddInputField(ctx, usage, size)) { + return false; + } + } + ctx->field_offset += size; + } + return true; +} + +static bool ParseMainItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + unsigned int flags; + + switch (tag) { + case MainTagInput: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagInput", flags); + AddInputFields(ctx); + break; + case MainTagOutput: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagOutput", flags); + break; + case MainTagFeature: + flags = ReadValue(data, size); + DebugMainTag(ctx, "MainTagFeature", flags); + break; + case MainTagCollection: + DebugDescriptor(ctx, "MainTagCollection"); + switch (*data) { + case 0x00: + DebugDescriptor(ctx, "Physical"); + break; + case 0x01: + DebugDescriptor(ctx, "Application"); + break; + case 0x02: + DebugDescriptor(ctx, "Logical"); + break; + case 0x03: + DebugDescriptor(ctx, "Report"); + break; + case 0x04: + DebugDescriptor(ctx, "Named Array"); + break; + case 0x05: + DebugDescriptor(ctx, "Usage Switch"); + break; + case 0x06: + DebugDescriptor(ctx, "Usage Modifier"); + break; + default: + break; + } + ++ctx->collection_depth; + break; + case MainTagEndCollection: + if (ctx->collection_depth > 0) { + --ctx->collection_depth; + } + DebugDescriptor(ctx, "MainTagEndCollection"); + break; + default: + DebugDescriptor(ctx, "Unknown main tag: %d", tag); + break; + } + + ResetLocalState(ctx); + + return true; +} + +static bool ParseGlobalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + unsigned int value; + + switch (tag) { + case GlobalTagUsagePage: + ctx->global.usage_page = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagUsagePage: 0x%.4x", ctx->global.usage_page); + break; + case GlobalTagLogicalMinimum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagLogicalMinimum: %u", value); + break; + case GlobalTagLogicalMaximum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagLogicalMaximum: %u", value); + break; + case GlobalTagPhysicalMinimum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagPhysicalMinimum: %u", value); + break; + case GlobalTagPhysicalMaximum: + value = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagPhysicalMaximum: %u", value); + break; + case GlobalTagUnitExponent: + DebugDescriptor(ctx, "GlobalTagUnitExponent"); + break; + case GlobalTagUnit: + DebugDescriptor(ctx, "GlobalTagUnit"); + break; + case GlobalTagReportSize: + ctx->global.report_size = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagReportSize: %u", ctx->global.report_size); + break; + case GlobalTagReportID: + ctx->global.report_id = ReadValue(data, size); + ctx->field_offset = 0; + DebugDescriptor(ctx, "GlobalTagReportID: %u", ctx->global.report_id); + break; + case GlobalTagReportCount: + ctx->global.report_count = ReadValue(data, size); + DebugDescriptor(ctx, "GlobalTagReportCount: %u", ctx->global.report_count); + break; + case GlobalTagPush: + DebugDescriptor(ctx, "GlobalTagPush"); + break; + case GlobalTagPop: + DebugDescriptor(ctx, "GlobalTagPop"); + break; + default: + DebugDescriptor(ctx, "Unknown global tag"); + break; + } + return true; +} + +static bool ParseLocalItem(DescriptorContext *ctx, int tag, int size, const Uint8 *data) +{ + unsigned int value; + + switch (tag) { + case LocalTagUsage: + value = ReadValue(data, size); + AddUsage(ctx, value); + DebugDescriptor(ctx, "LocalTagUsage: 0x%.4x", value); + break; + case LocalTagUsageMinimum: + ctx->local.usage_minimum = ReadValue(data, size); + DebugDescriptor(ctx, "LocalTagUsageMinimum: 0x%.4x", ctx->local.usage_minimum); + break; + case LocalTagUsageMaximum: + ctx->local.usage_maximum = ReadValue(data, size); + DebugDescriptor(ctx, "LocalTagUsageMaximum: 0x%.4x", ctx->local.usage_maximum); + break; + case LocalTagDesignatorIndex: + DebugDescriptor(ctx, "LocalTagDesignatorIndex"); + break; + case LocalTagDesignatorMinimum: + DebugDescriptor(ctx, "LocalTagDesignatorMinimum"); + break; + case LocalTagDesignatorMaximum: + DebugDescriptor(ctx, "LocalTagDesignatorMaximum"); + break; + case LocalTagStringIndex: + DebugDescriptor(ctx, "LocalTagStringIndex"); + break; + case LocalTagStringMinimum: + DebugDescriptor(ctx, "LocalTagStringMinimum"); + break; + case LocalTagStringMaximum: + DebugDescriptor(ctx, "LocalTagStringMaximum"); + break; + case LocalTagDelimiter: + DebugDescriptor(ctx, "LocalTagDelimiter"); + break; + default: + DebugDescriptor(ctx, "Unknown local tag"); + break; + } + return true; +} + +bool ParseDescriptor(DescriptorContext *ctx, const Uint8 *descriptor, int descriptor_size) +{ + SDL_zerop(ctx); + + for (const Uint8 *here = descriptor; here < descriptor + descriptor_size; ) { + static const int sizes[4] = { 0, 1, 2, 4 }; + Uint8 data = *here++; + int size = sizes[(data & 0x3)]; + int type = ((data >> 2) & 0x3); + int tag = (data >> 4); + + if ((here + size) > (descriptor + descriptor_size)) { + return SDL_SetError("Invalid descriptor"); + } + +#ifdef DEBUG_DESCRIPTOR + SDL_Log("Data: 0x%.2x, size: %d, type: %d, tag: %d", data, size, type, tag); +#endif + switch (type) { + case DescriptorItemTypeMain: + if (!ParseMainItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeGlobal: + if (!ParseGlobalItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeLocal: + if (!ParseLocalItem(ctx, tag, size, here)) { + return false; + } + break; + case DescriptorItemTypeReserved: + // Long items are currently unsupported + return SDL_Unsupported(); + } + + here += size; + } + return true; +} + +static void CleanupContext(DescriptorContext *ctx) +{ + SDL_free(ctx->local.usages); + SDL_free(ctx->fields); +} + +SDL_ReportDescriptor *SDL_ParseReportDescriptor(const Uint8 *descriptor, int descriptor_size) +{ + SDL_ReportDescriptor *result = NULL; + + DescriptorContext ctx; + if (ParseDescriptor(&ctx, descriptor, descriptor_size)) { + result = (SDL_ReportDescriptor *)SDL_malloc(sizeof(*result)); + if (result) { + result->field_count = ctx.field_count; + result->fields = ctx.fields; + ctx.fields = NULL; + } + } + CleanupContext(&ctx); + + return result; +} + +bool SDL_DescriptorHasUsage(SDL_ReportDescriptor *descriptor, Uint16 usage_page, Uint16 usage) +{ + if (!descriptor) { + return false; + } + + Uint32 full_usage = (((Uint32)usage_page << 16) | usage); + for (int i = 0; i < descriptor->field_count; ++i) { + if (descriptor->fields[i].usage == full_usage) { + return true; + } + } + return false; +} + +void SDL_DestroyDescriptor(SDL_ReportDescriptor *descriptor) +{ + if (descriptor) { + SDL_free(descriptor->fields); + SDL_free(descriptor); + } +} + +bool SDL_ReadReportData(const Uint8 *data, int size, int bit_offset, int bit_size, unsigned int *value) +{ + int offset = (bit_offset / 8); + if (offset >= size) { + *value = 0; + return SDL_SetError("Out of bounds reading report data"); + } + + *value = ReadValue(data + offset, (bit_size + 7) / 8); + + int shift = (bit_offset % 8); + if (shift > 0) { + *value >>= shift; + } + + switch (bit_size) { + case 1: + *value &= 0x1; + break; + case 4: + *value &= 0xf; + break; + case 10: + *value &= 0x3ff; + break; + case 15: + *value &= 0x7fff; + break; + default: + SDL_assert((bit_size % 8) == 0); + break; + } + return true; +} + +#ifdef TEST_MAIN + +#include + +int main(int argc, char *argv[]) +{ + const char *file = argv[1]; + if (argc < 2) { + SDL_Log("Usage: %s file", argv[0]); + return 1; + } + + size_t descriptor_size = 0; + Uint8 *descriptor = SDL_LoadFile(argv[1], &descriptor_size); + if (!descriptor) { + SDL_Log("Couldn't load %s: %s", argv[1], SDL_GetError()); + return 2; + } + + DescriptorContext ctx; + if (!ParseDescriptor(&ctx, descriptor, descriptor_size)) { + SDL_Log("Couldn't parse %s: %s", argv[1], SDL_GetError()); + return 3; + } + return 0; +} + +#endif // TEST_MAIN diff --git a/src/joystick/hidapi/SDL_report_descriptor.h b/src/joystick/hidapi/SDL_report_descriptor.h new file mode 100644 index 0000000000..8113ec523e --- /dev/null +++ b/src/joystick/hidapi/SDL_report_descriptor.h @@ -0,0 +1,40 @@ +/* + Simple DirectMedia Layer + Copyright (C) 1997-2025 Sam Lantinga + + 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); diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index f34d843fd4..53d8e9e6ba 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -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