From 1be08afec5e3541514ac353fa77c15eb6515567a Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 3 Jul 2026 08:05:00 -0700
Subject: [PATCH] Added support for several Turtle Beach Nintendo Switch 2
controllers
* Turtle Beach Afterglow Wired Controller for Nintendo Switch 2
* Turtle Beach Afterglow Wave Wired Controller for Nintendo Switch 2
* Turtle Beach Afterglow Wireless RGB Gaming Controller for Nintendo Switch 2
* Turtle Beach Rematch Wired Controller for Nintendo Switch 2
* Turtle Beach Rematch Wireless RGB Gaming Controller for Nintendo Switch 2
---
src/SDL_utils.c | 1 +
src/joystick/SDL_gamepad.c | 6 +++-
src/joystick/SDL_joystick.c | 15 +++++++++-
src/joystick/SDL_joystick_c.h | 2 ++
src/joystick/controller_list.h | 4 +++
src/joystick/controller_type.h | 1 +
src/joystick/hidapi/SDL_hidapi_switch.c | 37 ++++++++++++++++++++++---
7 files changed, 60 insertions(+), 6 deletions(-)
diff --git a/src/SDL_utils.c b/src/SDL_utils.c
index 50bfc8f5d5841..fdad1e57ad10a 100644
--- a/src/SDL_utils.c
+++ b/src/SDL_utils.c
@@ -500,6 +500,7 @@ char *SDL_CreateDeviceName(Uint16 vendor, Uint16 product, const char *vendor_nam
{ "Performance Designed Products", "PDP" },
{ "QANBA USA, LLC", "Qanba" },
{ "QANBA USA,LLC", "Qanba" },
+ { "Voyetra Turtle Beach,Inc.", "Turtle Beach" },
{ "Unknown ", "" },
};
char *name = NULL;
diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index e948fb13f1f94..2a693c28bb80d 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -717,7 +717,7 @@ static GamepadMapping_t *SDL_CreateMappingForAndroidGamepad(SDL_GUID guid)
int button_mask;
int axis_mask;
Uint16 vendor, product;
-
+
SDL_strlcpy(mapping_string, "none,", sizeof(mapping_string));
SDL_GetJoystickGUIDInfo(guid, &vendor, &product, NULL, NULL);
@@ -1265,6 +1265,10 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_GUID guid)
SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
// Nintendo Switch Pro controllers have a screenshot button
SDL_strlcat(mapping_string, "misc1:b11,", sizeof(mapping_string));
+ } else if (SDL_IsJoystickNintendoSwitch2Pro(vendor, product) ||
+ SDL_IsJoystickNintendoSwitch2ProInputOnly(vendor, product)) {
+ // Nintendo Switch 2 Pro controllers have a screenshot button and C button
+ SDL_strlcat(mapping_string, "misc1:b11,misc2:b12", sizeof(mapping_string));
} else if (SDL_IsJoystickNintendoSwitchJoyConPair(vendor, product)) {
// The Nintendo Switch Joy-Con combined controllers has a share button and paddles
SDL_strlcat(mapping_string, "misc1:b11,paddle1:b12,paddle2:b13,paddle3:b14,paddle4:b15,", sizeof(mapping_string));
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index fe46370652259..c8ad87bd494ba 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -3131,6 +3131,7 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
case k_eControllerType_SwitchProController:
case k_eControllerType_Switch2ProController:
case k_eControllerType_SwitchInputOnlyController:
+ case k_eControllerType_Switch2InputOnlyController:
type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
break;
case k_eControllerType_XInputSwitchController:
@@ -3255,16 +3256,28 @@ bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id)
{
EControllerType eType = GuessControllerType(vendor_id, product_id);
return eType == k_eControllerType_SwitchProController ||
- eType == k_eControllerType_Switch2ProController ||
eType == k_eControllerType_SwitchInputOnlyController;
}
+bool SDL_IsJoystickNintendoSwitch2Pro(Uint16 vendor_id, Uint16 product_id)
+{
+ EControllerType eType = GuessControllerType(vendor_id, product_id);
+ return eType == k_eControllerType_Switch2ProController ||
+ eType == k_eControllerType_Switch2InputOnlyController;
+}
+
bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id)
{
EControllerType eType = GuessControllerType(vendor_id, product_id);
return eType == k_eControllerType_SwitchInputOnlyController;
}
+bool SDL_IsJoystickNintendoSwitch2ProInputOnly(Uint16 vendor_id, Uint16 product_id)
+{
+ EControllerType eType = GuessControllerType(vendor_id, product_id);
+ return eType == k_eControllerType_Switch2InputOnlyController;
+}
+
bool SDL_IsJoystickNintendoSwitchJoyCon(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 69e42b02094a1..c4d940edfaae4 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -107,7 +107,9 @@ extern bool SDL_IsJoystickDualSenseEdge(Uint16 vendor_id, Uint16 product_id);
// Function to return whether a joystick is a Nintendo Switch Pro controller
extern bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id);
+extern bool SDL_IsJoystickNintendoSwitch2Pro(Uint16 vendor_id, Uint16 product_id);
extern bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id);
+extern bool SDL_IsJoystickNintendoSwitch2ProInputOnly(Uint16 vendor_id, Uint16 product_id);
extern bool SDL_IsJoystickNintendoSwitchJoyCon(Uint16 vendor_id, Uint16 product_id);
extern bool SDL_IsJoystickNintendoSwitchJoyConLeft(Uint16 vendor_id, Uint16 product_id);
extern bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id);
diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h
index 3ef20db39c4b6..c0093acda45e8 100644
--- a/src/joystick/controller_list.h
+++ b/src/joystick/controller_list.h
@@ -623,6 +623,10 @@ static const ControllerDescription_t arrControllers[] = {
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0188 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PDP Afterglow Wired Deluxe+ Audio Controller
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x018b ), k_eControllerType_SwitchProController, NULL }, // PDP Afterglow Wave Wireless Controller for Switch
{ MAKE_CONTROLLER_ID( 0x0e6f, 0x018c ), k_eControllerType_SwitchProController, "PDP REALMz Wireless Controller" }, // PDP REALMz Wireless Controller for Switch
+ { MAKE_CONTROLLER_ID( 0x0e6f, 0x0193 ), k_eControllerType_Switch2ProController, NULL }, // Turtle Beach Afterglow Wireless RGB Gaming Controller for Nintendo Switch 2 and the Turtle Beach Rematch Wireless RGB Gaming Controller for Nintendo Switch 2
+ { MAKE_CONTROLLER_ID( 0x0e6f, 0x0196 ), k_eControllerType_Switch2InputOnlyController, NULL }, // Turtle Beach Rematch Wired Controller for Nintendo Switch 2
+ { MAKE_CONTROLLER_ID( 0x0e6f, 0x0197 ), k_eControllerType_Switch2InputOnlyController, NULL }, // Turtle Beach Afterglow Wave Wired Controller for Nintendo Switch 2
+ { MAKE_CONTROLLER_ID( 0x0e6f, 0x0198 ), k_eControllerType_Switch2InputOnlyController, NULL }, // Turtle Beach Afterglow Wired Controller for Switch 2
{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00aa ), k_eControllerType_SwitchInputOnlyController, NULL }, // HORI Real Arcade Pro V Hayabusa in Switch Mode
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa711 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Wired Controller Plus/PowerA Wired Controller Nintendo GameCube Style
{ MAKE_CONTROLLER_ID( 0x20d6, 0xa712 ), k_eControllerType_SwitchInputOnlyController, NULL }, // PowerA Nintendo Switch Fusion Fight Pad
diff --git a/src/joystick/controller_type.h b/src/joystick/controller_type.h
index 028a83dfacdd8..94d75dafcb2b7 100644
--- a/src/joystick/controller_type.h
+++ b/src/joystick/controller_type.h
@@ -64,6 +64,7 @@ typedef enum
k_eControllerType_HoriSteamController = 49,
k_eControllerType_8BitDoController = 50,
k_eControllerType_Switch2ProController = 51,
+ k_eControllerType_Switch2InputOnlyController = 52,
k_eControllerType_LastController, // Don't add game controllers below this enumeration - this enumeration can change value
// Keyboards and Mice
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 5b0b5bfa95ea3..fe895dbbff282 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -73,6 +73,19 @@ enum
SDL_GAMEPAD_NUM_SWITCH_BUTTONS,
};
+enum
+{
+ SDL_GAMEPAD_BUTTON_SWITCH_INPUT_ONLY_SHARE = 11,
+ SDL_GAMEPAD_NUM_SWITCH_INPUT_ONLY_BUTTONS,
+};
+
+enum
+{
+ SDL_GAMEPAD_BUTTON_SWITCH2_SHARE = 11,
+ SDL_GAMEPAD_BUTTON_SWITCH2_C,
+ SDL_GAMEPAD_NUM_SWITCH2_BUTTONS,
+};
+
typedef enum
{
k_eSwitchInputReportIDs_SubcommandReply = 0x21,
@@ -286,6 +299,7 @@ typedef struct
SDL_HIDAPI_Device *device;
SDL_Joystick *joystick;
bool m_bInputOnly;
+ bool m_bSwitch2;
bool m_bUseButtonLabels;
bool m_bPlayerLights;
int m_nPlayerIndex;
@@ -1425,7 +1439,7 @@ static bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device, con
controller to continually attempt to reconnect is to filter it out by manufacturer/product string.
Note that the controller does have a different product string when connected over Bluetooth.
*/
- if (SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) {
+ if (name && SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) {
return false;
}
@@ -1561,7 +1575,9 @@ static bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
ctx->m_bSyncWrite = true;
// Find out whether or not we can send output reports
- ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id);
+ ctx->m_bSwitch2 = SDL_IsJoystickNintendoSwitch2Pro(device->vendor_id, device->product_id);
+ ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id) ||
+ SDL_IsJoystickNintendoSwitch2ProInputOnly(device->vendor_id, device->product_id);
if (!ctx->m_bInputOnly) {
// Initialize rumble data, important for reading device info on the MOBAPAD M073
SetNeutralRumble(device, &ctx->m_RumblePacket.rumbleData[0]);
@@ -1689,7 +1705,13 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys
SDL_PlayerLEDHintChanged, ctx);
// Initialize the joystick capabilities
- joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS;
+ if (ctx->m_bSwitch2) {
+ joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH2_BUTTONS;
+ } else if (ctx->m_bInputOnly) {
+ joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_INPUT_ONLY_BUTTONS;
+ } else {
+ joystick->nbuttons = SDL_GAMEPAD_NUM_SWITCH_BUTTONS;
+ }
joystick->naxes = SDL_GAMEPAD_AXIS_COUNT;
joystick->nhats = 1;
@@ -1944,7 +1966,11 @@ static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwi
if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) {
Uint8 hat;
- switch (packet->ucStickHat) {
+ if (ctx->m_bSwitch2) {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_C, ((packet->ucStickHat & 0x80) != 0));
+ }
+
+ switch (packet->ucStickHat & 0x0F) {
case 0:
hat = SDL_HAT_UP;
break;
@@ -2596,6 +2622,9 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_GUIDE, ((data & 0x10) != 0));
SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH_SHARE, ((data & 0x20) != 0));
+ if (ctx->m_bSwitch2) {
+ SDL_SendJoystickButton(timestamp, joystick, SDL_GAMEPAD_BUTTON_SWITCH2_C, ((data & 0x40) != 0));
+ }
}
if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) {