Added support for third party Nintendo Switch controllers that don't support the full protocol

This commit is contained in:
Sam Lantinga 2019-10-17 16:59:05 -07:00
parent 43c5f62d44
commit e6ac16ef2f
3 changed files with 179 additions and 56 deletions

View File

@ -1171,6 +1171,13 @@ SDL_IsJoystickNintendoSwitchPro(Uint16 vendor, Uint16 product)
eType == k_eControllerType_SwitchInputOnlyController); eType == k_eControllerType_SwitchInputOnlyController);
} }
SDL_bool
SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor, Uint16 product)
{
EControllerType eType = GuessControllerType(vendor, product);
return (eType == k_eControllerType_SwitchInputOnlyController);
}
SDL_bool SDL_bool
SDL_IsJoystickSteamController(Uint16 vendor, Uint16 product) SDL_IsJoystickSteamController(Uint16 vendor, Uint16 product)
{ {

View File

@ -55,7 +55,8 @@ extern void SDL_GetJoystickGUIDInfo(SDL_JoystickGUID guid, Uint16 *vendor, Uint1
extern SDL_bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id); extern SDL_bool SDL_IsJoystickPS4(Uint16 vendor_id, Uint16 product_id);
/* Function to return whether a joystick is a Nintendo Switch Pro controller */ /* Function to return whether a joystick is a Nintendo Switch Pro controller */
extern SDL_bool SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id); extern SDL_bool SDL_IsJoystickNintendoSwitchPro( Uint16 vendor_id, Uint16 product_id );
extern SDL_bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint16 product_id);
/* Function to return whether a joystick is a Steam Controller */ /* Function to return whether a joystick is a Steam Controller */
extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id); extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);

View File

@ -87,6 +87,14 @@ typedef enum {
#define k_unSPIStickCalibrationLength (k_unSPIStickCalibrationEndOffset - k_unSPIStickCalibrationStartOffset + 1) #define k_unSPIStickCalibrationLength (k_unSPIStickCalibrationEndOffset - k_unSPIStickCalibrationStartOffset + 1)
#pragma pack(1) #pragma pack(1)
typedef struct
{
Uint8 rgucButtons[2];
Uint8 ucStickHat;
Uint8 rgucJoystickLeft[2];
Uint8 rgucJoystickRight[2];
} SwitchInputOnlyControllerStatePacket_t;
typedef struct typedef struct
{ {
Uint8 rgucButtons[2]; Uint8 rgucButtons[2];
@ -135,11 +143,11 @@ typedef struct
#define k_unSubcommandDataBytes 35 #define k_unSubcommandDataBytes 35
union { union {
Uint8 rgucSubcommandData[ k_unSubcommandDataBytes ]; Uint8 rgucSubcommandData[k_unSubcommandDataBytes];
struct { struct {
SwitchSPIOpData_t opData; SwitchSPIOpData_t opData;
Uint8 rgucReadData[ k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t) ]; Uint8 rgucReadData[k_unSubcommandDataBytes - sizeof(SwitchSPIOpData_t)];
} spiReadData; } spiReadData;
struct { struct {
@ -170,7 +178,7 @@ typedef struct
SwitchCommonOutputPacket_t commonData; SwitchCommonOutputPacket_t commonData;
Uint8 ucSubcommandID; Uint8 ucSubcommandID;
Uint8 rgucSubcommandData[ k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1 ]; Uint8 rgucSubcommandData[k_unSwitchOutputPacketDataLength - sizeof(SwitchCommonOutputPacket_t) - 1];
} SwitchSubcommandOutputPacket_t; } SwitchSubcommandOutputPacket_t;
typedef struct typedef struct
@ -178,17 +186,20 @@ typedef struct
Uint8 ucPacketType; Uint8 ucPacketType;
Uint8 ucProprietaryID; Uint8 ucProprietaryID;
Uint8 rgucProprietaryData[ k_unSwitchOutputPacketDataLength - 1 - 1 ]; Uint8 rgucProprietaryData[k_unSwitchOutputPacketDataLength - 1 - 1];
} SwitchProprietaryOutputPacket_t; } SwitchProprietaryOutputPacket_t;
#pragma pack() #pragma pack()
typedef struct { typedef struct {
hid_device *dev; hid_device *dev;
SDL_bool m_bIsInputOnly;
SDL_bool m_bIsUsingBluetooth; SDL_bool m_bIsUsingBluetooth;
Uint8 m_nCommandNumber; Uint8 m_nCommandNumber;
SwitchCommonOutputPacket_t m_RumblePacket; SwitchCommonOutputPacket_t m_RumblePacket;
Uint32 m_nRumbleExpiration; Uint32 m_nRumbleExpiration;
Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength]; Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength];
SwitchInputOnlyControllerStatePacket_t m_lastInputOnlyState;
SwitchSimpleStatePacket_t m_lastSimpleState; SwitchSimpleStatePacket_t m_lastSimpleState;
SwitchStatePacket_t m_lastFullState; SwitchStatePacket_t m_lastFullState;
@ -245,7 +256,7 @@ static SwitchSubcommandInputPacket_t *ReadSubcommandReply(SDL_DriverSwitch_Conte
while ((nRead = ReadInput(ctx)) != -1) { while ((nRead = ReadInput(ctx)) != -1) {
if (nRead > 0) { if (nRead > 0) {
if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) { if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_SubcommandReply) {
SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[ 1 ]; SwitchSubcommandInputPacket_t *reply = (SwitchSubcommandInputPacket_t *)&ctx->m_rgucReadBuffer[1];
if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) { if (reply->ucSubcommandID == expectedID && (reply->ucSubcommandAck & 0x80)) {
return reply; return reply;
} }
@ -270,7 +281,7 @@ static SDL_bool ReadProprietaryReply(SDL_DriverSwitch_Context *ctx, ESwitchPropr
int nRead = 0; int nRead = 0;
while ((nRead = ReadInput(ctx)) != -1) { while ((nRead = ReadInput(ctx)) != -1) {
if (nRead > 0) { if (nRead > 0) {
if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[ 1 ] == expectedID) { if (ctx->m_rgucReadBuffer[0] == k_eSwitchInputReportIDs_CommandAck && ctx->m_rgucReadBuffer[1] == expectedID) {
return SDL_TRUE; return SDL_TRUE;
} }
} else { } else {
@ -584,52 +595,56 @@ HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_
*context = ctx; *context = ctx;
/* Initialize rumble data */ /* Find out whether or not we can send output reports */
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); ctx->m_bIsInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(vendor_id, product_id);
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); if (!ctx->m_bIsInputOnly) {
/* Initialize rumble data */
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]);
SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
/* Try setting up USB mode, and if that fails we're using Bluetooth */ /* Try setting up USB mode, and if that fails we're using Bluetooth */
if (!BTrySetupUSB(ctx)) { if (!BTrySetupUSB(ctx)) {
ctx->m_bIsUsingBluetooth = SDL_TRUE; ctx->m_bIsUsingBluetooth = SDL_TRUE;
} }
if (!LoadStickCalibration(ctx)) { if (!LoadStickCalibration(ctx)) {
SDL_SetError("Couldn't load stick calibration"); SDL_SetError("Couldn't load stick calibration");
SDL_free(ctx);
return SDL_FALSE;
}
if (!SetVibrationEnabled(ctx, 1)) {
SDL_SetError("Couldn't enable vibration");
SDL_free(ctx);
return SDL_FALSE;
}
/* Set the desired input mode */
if (ctx->m_bIsUsingBluetooth) {
input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
} else {
input_mode = k_eSwitchInputReportIDs_FullControllerState;
}
if (!SetInputMode(ctx, input_mode)) {
SDL_SetError("Couldn't set input mode");
SDL_free(ctx);
return SDL_FALSE;
}
/* Start sending USB reports */
if (!ctx->m_bIsUsingBluetooth) {
/* ForceUSB doesn't generate an ACK, so don't wait for a reply */
if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE)) {
SDL_SetError("Couldn't start USB reports");
SDL_free(ctx); SDL_free(ctx);
return SDL_FALSE; return SDL_FALSE;
} }
}
/* Set the LED state */ if (!SetVibrationEnabled(ctx, 1)) {
SetHomeLED(ctx, 100); SDL_SetError("Couldn't enable vibration");
SetSlotLED(ctx, (joystick->instance_id % 4)); SDL_free(ctx);
return SDL_FALSE;
}
/* Set the desired input mode */
if (ctx->m_bIsUsingBluetooth) {
input_mode = k_eSwitchInputReportIDs_SimpleControllerState;
} else {
input_mode = k_eSwitchInputReportIDs_FullControllerState;
}
if (!SetInputMode(ctx, input_mode)) {
SDL_SetError("Couldn't set input mode");
SDL_free(ctx);
return SDL_FALSE;
}
/* Start sending USB reports */
if (!ctx->m_bIsUsingBluetooth) {
/* ForceUSB doesn't generate an ACK, so don't wait for a reply */
if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE)) {
SDL_SetError("Couldn't start USB reports");
SDL_free(ctx);
return SDL_FALSE;
}
}
/* Set the LED state */
SetHomeLED(ctx, 100);
SetSlotLED(ctx, (joystick->instance_id % 4));
}
/* Initialize the joystick capabilities */ /* Initialize the joystick capabilities */
joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX;
@ -680,6 +695,102 @@ HIDAPI_DriverSwitch_Rumble(SDL_Joystick *joystick, hid_device *dev, void *contex
return 0; return 0;
} }
static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchInputOnlyControllerStatePacket_t *packet)
{
Sint16 axis;
if (packet->rgucButtons[0] != ctx->m_lastInputOnlyState.rgucButtons[0]) {
Uint8 data = packet->rgucButtons[0];
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED);
axis = (data & 0x40) ? 32767 : -32768;
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis);
axis = (data & 0x80) ? 32767 : -32768;
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis);
}
if (packet->rgucButtons[1] != ctx->m_lastInputOnlyState.rgucButtons[1]) {
Uint8 data = packet->rgucButtons[1];
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED);
}
if (packet->ucStickHat != ctx->m_lastInputOnlyState.ucStickHat) {
SDL_bool dpad_up = SDL_FALSE;
SDL_bool dpad_down = SDL_FALSE;
SDL_bool dpad_left = SDL_FALSE;
SDL_bool dpad_right = SDL_FALSE;
switch (packet->ucStickHat) {
case 0:
dpad_up = SDL_TRUE;
break;
case 1:
dpad_up = SDL_TRUE;
dpad_right = SDL_TRUE;
break;
case 2:
dpad_right = SDL_TRUE;
break;
case 3:
dpad_right = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 4:
dpad_down = SDL_TRUE;
break;
case 5:
dpad_left = SDL_TRUE;
dpad_down = SDL_TRUE;
break;
case 6:
dpad_left = SDL_TRUE;
break;
case 7:
dpad_up = SDL_TRUE;
dpad_left = SDL_TRUE;
break;
default:
break;
}
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, dpad_down);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, dpad_up);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, dpad_right);
SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, dpad_left);
}
if (packet->rgucJoystickLeft[0] != ctx->m_lastInputOnlyState.rgucJoystickLeft[0]) {
axis = (Sint16)(RemapVal(packet->rgucJoystickLeft[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16));
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis);
}
if (packet->rgucJoystickLeft[1] != ctx->m_lastInputOnlyState.rgucJoystickLeft[1]) {
axis = (Sint16)(RemapVal(packet->rgucJoystickLeft[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16));
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis);
}
if (packet->rgucJoystickRight[0] != ctx->m_lastInputOnlyState.rgucJoystickRight[0]) {
axis = (Sint16)(RemapVal(packet->rgucJoystickRight[0], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16));
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis);
}
if (packet->rgucJoystickRight[1] != ctx->m_lastInputOnlyState.rgucJoystickRight[1]) {
axis = (Sint16)(RemapVal(packet->rgucJoystickRight[1], SDL_MIN_UINT8, SDL_MAX_UINT8, SDL_MIN_SINT16, SDL_MAX_SINT16));
SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, axis);
}
ctx->m_lastInputOnlyState = *packet;
}
static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet) static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchSimpleStatePacket_t *packet)
{ {
/* 0x8000 is the neutral value for all joystick axes */ /* 0x8000 is the neutral value for all joystick axes */
@ -853,15 +964,19 @@ HIDAPI_DriverSwitch_Update(SDL_Joystick *joystick, hid_device *dev, void *contex
int size; int size;
while ((size = ReadInput(ctx)) > 0) { while ((size = ReadInput(ctx)) > 0) {
switch (ctx->m_rgucReadBuffer[0]) { if (ctx->m_bIsInputOnly) {
case k_eSwitchInputReportIDs_SimpleControllerState: HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]);
HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]); } else {
break; switch (ctx->m_rgucReadBuffer[0]) {
case k_eSwitchInputReportIDs_FullControllerState: case k_eSwitchInputReportIDs_SimpleControllerState:
HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]); HandleSimpleControllerState(joystick, ctx, (SwitchSimpleStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
break; break;
default: case k_eSwitchInputReportIDs_FullControllerState:
break; HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]);
break;
default:
break;
}
} }
} }