diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c index 5f08f94752..32d76df011 100644 --- a/src/joystick/SDL_gamepad.c +++ b/src/joystick/SDL_gamepad.c @@ -779,6 +779,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) } break; } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SN30_PRO || + product == USB_PRODUCT_8BITDO_SN30_PRO_BT || + product == USB_PRODUCT_8BITDO_PRO_2 || + product == USB_PRODUCT_8BITDO_PRO_2_BT)) { + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); + if (product == USB_PRODUCT_8BITDO_PRO_2 || product == USB_PRODUCT_8BITDO_PRO_2_BT) { + SDL_strlcat(mapping_string, "paddle1:b14,paddle2:b13,", sizeof(mapping_string)); + } + } else if (vendor == USB_VENDOR_8BITDO && + (product == USB_PRODUCT_8BITDO_SF30_PRO || + product == USB_PRODUCT_8BITDO_SF30_PRO_BT)) { + // This controller has no guide button + SDL_strlcat(mapping_string, "a:b1,b:b0,back:b4,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b3,y:b2,hint:!SDL_GAMECONTROLLER_USE_BUTTON_LABELS:=1,", sizeof(mapping_string)); } else { // All other gamepads have the standard set of 19 buttons and 6 axes if (SDL_IsJoystickGameCube(vendor, product)) { @@ -802,20 +816,20 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid) SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); } else if (SDL_IsJoystickGoogleStadiaController(vendor, product)) { // The Google Stadia controller has a share button and a Google Assistant button - SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "misc1:b11,misc2:b12,", sizeof(mapping_string)); } else if (SDL_IsJoystickNVIDIASHIELDController(vendor, product)) { // The NVIDIA SHIELD controller has a share button between back and start buttons SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string)); if (product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER_V103) { // The original SHIELD controller has a touchpad and plus/minus buttons as well - SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "touchpad:b12,misc2:b13,misc3:b14,", sizeof(mapping_string)); } } else if (SDL_IsJoystickHoriSteamController(vendor, product)) { /* The Wireless HORIPad for Steam has QAM, Steam, Capsense L/R Sticks, 2 rear buttons, and 2 misc buttons */ - SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17", sizeof(mapping_string)); - } else if (SDL_IsJoystick8BitDoController(vendor, product)) { - SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13", sizeof(mapping_string)); + SDL_strlcat(mapping_string, "paddle1:b13,paddle2:b12,paddle3:b15,paddle4:b14,misc2:b11,misc3:b16,misc4:b17,", sizeof(mapping_string)); + } else if (vendor == USB_VENDOR_8BITDO && product == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + SDL_strlcat(mapping_string, "paddle1:b12,paddle2:b11,paddle3:b14,paddle4:b13,", sizeof(mapping_string)); } else { switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) { case SDL_GAMEPAD_TYPE_PS4: @@ -1295,7 +1309,7 @@ static bool SDL_PrivateParseGamepadElement(SDL_Gamepad *gamepad, const char *szG static bool SDL_PrivateParseGamepadConfigString(SDL_Gamepad *gamepad, const char *pchString) { char szGameButton[20]; - char szJoystickButton[20]; + char szJoystickButton[128]; bool bGameButton = true; int i = 0; const char *pchPos = pchString; diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index c88b15cbe3..3caf227f01 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -3175,24 +3175,6 @@ bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id) return vendor_id == USB_VENDOR_HORI && (product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER || product_id == USB_PRODUCT_HORI_STEAM_CONTROLLER_BT); } -bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id) -{ - if (vendor_id == USB_VENDOR_8BITDO) { - switch (product_id) { - case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: - case USB_PRODUCT_8BITDO_SN30_PRO: - case USB_PRODUCT_8BITDO_SN30_PRO_BT: - case USB_PRODUCT_8BITDO_SF30_PRO: - case USB_PRODUCT_8BITDO_PRO_2: - case USB_PRODUCT_8BITDO_PRO_2_BT: - return true; - default: - break; - } - } - return false; -} - bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id) { EControllerType eType = GuessControllerType(vendor_id, product_id); diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h index 61c7b32bbe..6b82365c58 100644 --- a/src/joystick/SDL_joystick_c.h +++ b/src/joystick/SDL_joystick_c.h @@ -135,9 +135,6 @@ extern bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); // Function to return whether a joystick is a HORI Steam controller extern bool SDL_IsJoystickHoriSteamController(Uint16 vendor_id, Uint16 product_id); -// Function to return whether a joystick is a 8BitDo controller -extern bool SDL_IsJoystick8BitDoController(Uint16 vendor_id, Uint16 product_id); - // Function to return whether a joystick is a Steam Deck extern bool SDL_IsJoystickSteamDeck(Uint16 vendor_id, Uint16 product_id); diff --git a/src/joystick/hidapi/SDL_hidapi_8bitdo.c b/src/joystick/hidapi/SDL_hidapi_8bitdo.c index 1f12f85957..82efaf7019 100644 --- a/src/joystick/hidapi/SDL_hidapi_8bitdo.c +++ b/src/joystick/hidapi/SDL_hidapi_8bitdo.c @@ -126,7 +126,21 @@ static int ReadFeatureReport(SDL_hid_device *dev, Uint8 report_id, Uint8 *report static bool HIDAPI_Driver8BitDo_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) { - return SDL_IsJoystick8BitDoController(vendor_id, product_id); + if (vendor_id == USB_VENDOR_8BITDO) { + switch (product_id) { + case USB_PRODUCT_8BITDO_SF30_PRO: + case USB_PRODUCT_8BITDO_SF30_PRO_BT: + case USB_PRODUCT_8BITDO_SN30_PRO: + case USB_PRODUCT_8BITDO_SN30_PRO_BT: + case USB_PRODUCT_8BITDO_PRO_2: + case USB_PRODUCT_8BITDO_PRO_2_BT: + case USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS: + return true; + default: + break; + } + } + return false; } static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) @@ -147,21 +161,24 @@ static bool HIDAPI_Driver8BitDo_InitDevice(SDL_HIDAPI_Device *device) ctx->rumble_supported = true; ctx->powerstate_supported = true; } - } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT || - device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_PRO_2 || - device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + } else { Uint8 data[USB_PACKET_LENGTH]; int size = ReadFeatureReport(device->dev, SDL_8BITDO_FEATURE_REPORTID_ENABLE_SDL_REPORTID, data, sizeof(data)); if (size > 0) { ctx->sensors_supported = true; ctx->rumble_supported = true; ctx->powerstate_supported = true; - } else { - SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, - "HIDAPI_Driver8BitDo_InitDevice(): Couldn't read feature report 0x06"); } } + if (device->product_id == USB_PRODUCT_8BITDO_SF30_PRO || device->product_id == USB_PRODUCT_8BITDO_SF30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SF30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_SN30_PRO || device->product_id == USB_PRODUCT_8BITDO_SN30_PRO_BT) { + HIDAPI_SetDeviceName(device, "8BitDo SN30 Pro"); + } else if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT) { + HIDAPI_SetDeviceName(device, "8BitDo Pro 2"); + } + return HIDAPI_JoystickConnected(device, NULL); } @@ -187,7 +204,14 @@ static bool HIDAPI_Driver8BitDo_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys SDL_zeroa(ctx->last_state); // Initialize the joystick capabilities - joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + if (device->product_id == USB_PRODUCT_8BITDO_PRO_2 || + device->product_id == USB_PRODUCT_8BITDO_PRO_2_BT || + device->product_id == USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS) { + // This controller has additional buttons + joystick->nbuttons = SDL_GAMEPAD_NUM_8BITDO_BUTTONS; + } else { + joystick->nbuttons = 11; + } joystick->naxes = SDL_GAMEPAD_AXIS_COUNT; joystick->nhats = 1; @@ -257,12 +281,95 @@ static bool HIDAPI_Driver8BitDo_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *dev } return SDL_Unsupported(); } + +static void HIDAPI_Driver8BitDo_HandleOldStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) +{ + Sint16 axis; + Uint64 timestamp = SDL_GetTicksNS(); + + if (ctx->last_state[2] != data[2]) { + Uint8 hat; + + switch (data[2]) { + case 0: + hat = SDL_HAT_UP; + break; + case 1: + hat = SDL_HAT_RIGHTUP; + break; + case 2: + hat = SDL_HAT_RIGHT; + break; + case 3: + hat = SDL_HAT_RIGHTDOWN; + break; + case 4: + hat = SDL_HAT_DOWN; + break; + case 5: + hat = SDL_HAT_LEFTDOWN; + break; + case 6: + hat = SDL_HAT_LEFT; + break; + case 7: + hat = SDL_HAT_LEFTUP; + break; + default: + hat = SDL_HAT_CENTERED; + break; + } + SDL_SendJoystickHat(timestamp, joystick, 0, hat); + } + + if (ctx->last_state[0] != data[0]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SOUTH, ((data[0] & 0x01) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_EAST, ((data[0] & 0x02) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_WEST, ((data[0] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_NORTH, ((data[0] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, ((data[0] & 0x40) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, ((data[0] & 0x80) != 0)); + } + + if (ctx->last_state[1] != data[1]) { + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data[1] & 0x10) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_BACK, ((data[1] & 0x04) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_START, ((data[1] & 0x08) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_LEFT_STICK, ((data[1] & 0x20) != 0)); + SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[1] & 0x40) != 0)); + + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFT_TRIGGER, (data[1] & 0x01) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER, (data[1] & 0x02) ? SDL_MAX_SINT16 : SDL_MIN_SINT16); + } + +#define READ_STICK_AXIS(offset) \ + (data[offset] == 0x7f ? 0 : (Sint16)HIDAPI_RemapVal((float)((int)data[offset] - 0x7f), -0x7f, 0xff - 0x7f, SDL_MIN_SINT16, SDL_MAX_SINT16)) + { + axis = READ_STICK_AXIS(3); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTX, axis); + axis = READ_STICK_AXIS(4); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_LEFTY, axis); + axis = READ_STICK_AXIS(5); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTX, axis); + axis = READ_STICK_AXIS(6); + SDL_SendJoystickAxis(timestamp, joystick, SDL_GAMEPAD_AXIS_RIGHTY, axis); + } +#undef READ_STICK_AXIS + + SDL_memcpy(ctx->last_state, data, SDL_min(size, sizeof(ctx->last_state))); +} + static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Driver8BitDo_Context *ctx, Uint8 *data, int size) { Sint16 axis; Uint64 timestamp = SDL_GetTicksNS(); - if (data[0] != SDL_8BITDO_REPORTID_SDL_REPORTID && data[0] != SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID && - data[0] != SDL_8BITDO_BT_REPORTID_SDL_REPORTID) { + + switch (data[0]) { + case SDL_8BITDO_REPORTID_NOT_SUPPORTED_SDL_REPORTID: // Firmware without enhanced mode + case SDL_8BITDO_REPORTID_SDL_REPORTID: // Enhanced mode USB report + case SDL_8BITDO_BT_REPORTID_SDL_REPORTID: // Enhanced mode Bluetooth report + break; + default: // We don't know how to handle this report return; } @@ -323,7 +430,7 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_RIGHT_STICK, ((data[9] & 0x40) != 0)); } - if (ctx->last_state[10] != data[10]) { + if (size > 10 && ctx->last_state[10] != data[10]) { SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_L4, ((data[10] & 0x01) != 0)); SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_8BITDO_R4, ((data[10] & 0x02) != 0)); } @@ -381,7 +488,6 @@ static void HIDAPI_Driver8BitDo_HandleStatePacket(SDL_Joystick *joystick, SDL_Dr SDL_SendJoystickPowerInfo(joystick, state, percent); } - if (ctx->sensors_enabled) { Uint64 sensor_timestamp; float values[3]; @@ -440,7 +546,12 @@ static bool HIDAPI_Driver8BitDo_UpdateDevice(SDL_HIDAPI_Device *device) continue; } - HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + if (size == 9) { + // Old firmware USB report for the SF30 Pro and SN30 Pro controllers + HIDAPI_Driver8BitDo_HandleOldStatePacket(joystick, ctx, data, size); + } else { + HIDAPI_Driver8BitDo_HandleStatePacket(joystick, ctx, data, size); + } } if (size < 0) { diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index 812d0a4805..323283f6ea 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -60,9 +60,10 @@ #define USB_VENDOR_ZEROPLUS 0x0c12 #define USB_PRODUCT_8BITDO_ULTIMATE2_WIRELESS 0x6012 +#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START +#define USB_PRODUCT_8BITDO_SF30_PRO_BT 0x6100 // B + START #define USB_PRODUCT_8BITDO_SN30_PRO 0x6001 // B + START #define USB_PRODUCT_8BITDO_SN30_PRO_BT 0x6101 // B + START -#define USB_PRODUCT_8BITDO_SF30_PRO 0x6000 // B + START #define USB_PRODUCT_8BITDO_PRO_2 0x6003 // mode switch to D #define USB_PRODUCT_8BITDO_PRO_2_BT 0x6006 // mode switch to D #define USB_PRODUCT_AMAZON_LUNA_CONTROLLER 0x0419