diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 9f75189d1..7703095c5 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -488,6 +488,29 @@ extern "C" { */ #define SDL_HINT_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT "SDL_GAMECONTROLLER_IGNORE_DEVICES_EXCEPT" +/** + * \brief If set, game controller face buttons report their values according to their labels instead of their positional layout. + * + * For example, on Nintendo Switch controllers, normally you'd get: + * + * (Y) + * (X) (B) + * (A) + * + * but if this hint is set, you'll get: + * + * (X) + * (Y) (A) + * (B) + * + * The variable can be set to the following values: + * "0" - Report the face buttons by position, as though they were on an Xbox controller. + * "1" - Report the face buttons by label instead of position + * + * The default value is "0". This hint may be set at any time. + */ +#define SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS "SDL_GAMECONTROLLER_USE_BUTTON_LABELS" + /** * \brief A variable that lets you enable joystick (and gamecontroller) events even when your app is in the background. * diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 229985f61..935c27be9 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -192,8 +192,9 @@ typedef struct typedef struct { hid_device *dev; - SDL_bool m_bIsInputOnly; - SDL_bool m_bIsUsingBluetooth; + SDL_bool m_bInputOnly; + SDL_bool m_bUsingBluetooth; + SDL_bool m_bUseButtonLabels; Uint8 m_nCommandNumber; SwitchCommonOutputPacket_t m_RumblePacket; Uint32 m_nRumbleExpiration; @@ -313,7 +314,7 @@ static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommand static SDL_bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen) { Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength]; - const size_t unWriteSize = ctx->m_bIsUsingBluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; + const size_t unWriteSize = ctx->m_bUsingBluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; if (ucLen > k_unSwitchOutputPacketDataLength) { return SDL_FALSE; @@ -526,7 +527,7 @@ static SDL_bool LoadStickCalibration(SDL_DriverSwitch_Context *ctx) } } - if (ctx->m_bIsUsingBluetooth) { + if (ctx->m_bUsingBluetooth) { for (stick = 0; stick < 2; ++stick) { for(axis = 0; axis < 2; ++axis) { ctx->m_StickExtents[stick].axis[axis].sMin = (Sint16)(SDL_MIN_SINT16 * 0.5f); @@ -580,6 +581,31 @@ static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, i return ApplyStickCalibrationCentered(ctx, nStick, nAxis, sRawValue, ctx->m_StickCalData[nStick].axis[nAxis].sCenter); } +static void SDLCALL SDL_GameControllerButtonReportingHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; + ctx->m_bUseButtonLabels = (hint && *hint != '0' && SDL_strcasecmp(hint, "false") != 0); +} + +static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button) +{ + if (ctx->m_bUseButtonLabels) { + switch (button) { + case SDL_CONTROLLER_BUTTON_A: + return SDL_CONTROLLER_BUTTON_B; + case SDL_CONTROLLER_BUTTON_B: + return SDL_CONTROLLER_BUTTON_A; + case SDL_CONTROLLER_BUTTON_X: + return SDL_CONTROLLER_BUTTON_Y; + case SDL_CONTROLLER_BUTTON_Y: + return SDL_CONTROLLER_BUTTON_X; + default: + break; + } + } + return button; +} + static SDL_bool HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_id, Uint16 product_id, void **context) { @@ -596,15 +622,15 @@ HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_ *context = ctx; /* Find out whether or not we can send output reports */ - ctx->m_bIsInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(vendor_id, product_id); - if (!ctx->m_bIsInputOnly) { + ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(vendor_id, product_id); + if (!ctx->m_bInputOnly) { /* 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 */ if (!BTrySetupUSB(ctx)) { - ctx->m_bIsUsingBluetooth = SDL_TRUE; + ctx->m_bUsingBluetooth = SDL_TRUE; } if (!LoadStickCalibration(ctx)) { @@ -620,7 +646,7 @@ HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_ } /* Set the desired input mode */ - if (ctx->m_bIsUsingBluetooth) { + if (ctx->m_bUsingBluetooth) { input_mode = k_eSwitchInputReportIDs_SimpleControllerState; } else { input_mode = k_eSwitchInputReportIDs_FullControllerState; @@ -632,7 +658,7 @@ HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_ } /* Start sending USB reports */ - if (!ctx->m_bIsUsingBluetooth) { + if (!ctx->m_bUsingBluetooth) { /* 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"); @@ -646,6 +672,14 @@ HIDAPI_DriverSwitch_Init(SDL_Joystick *joystick, hid_device *dev, Uint16 vendor_ SetSlotLED(ctx, (joystick->instance_id % 4)); } + if (vendor_id == 0x0e6f && product_id == 0x0185) { + /* This is a controller shaped like a GameCube controller, with a large central A button */ + ctx->m_bUseButtonLabels = SDL_TRUE; + } else { + SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, + SDL_GameControllerButtonReportingHintChanged, ctx); + } + /* Initialize the joystick capabilities */ joystick->nbuttons = SDL_CONTROLLER_BUTTON_MAX; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; @@ -701,10 +735,10 @@ static void HandleInputOnlyControllerState(SDL_Joystick *joystick, SDL_DriverSwi 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, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, 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); @@ -799,10 +833,10 @@ static void HandleSimpleControllerState(SDL_Joystick *joystick, SDL_DriverSwitch if (packet->rgucButtons[0] != ctx->m_lastSimpleState.rgucButtons[0]) { Uint8 data = packet->rgucButtons[0]; - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, 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); @@ -887,10 +921,10 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { Uint8 data = packet->controllerState.rgucButtons[0]; - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_X, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_Y, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_A, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_B, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_Y), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); axis = (data & 0x80) ? 32767 : -32768; SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); @@ -964,7 +998,7 @@ HIDAPI_DriverSwitch_Update(SDL_Joystick *joystick, hid_device *dev, void *contex int size; while ((size = ReadInput(ctx)) > 0) { - if (ctx->m_bIsInputOnly) { + if (ctx->m_bInputOnly) { HandleInputOnlyControllerState(joystick, ctx, (SwitchInputOnlyControllerStatePacket_t *)&ctx->m_rgucReadBuffer[0]); } else { switch (ctx->m_rgucReadBuffer[0]) { @@ -995,8 +1029,13 @@ HIDAPI_DriverSwitch_Quit(SDL_Joystick *joystick, hid_device *dev, void *context) { SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)context; - /* Restore simple input mode for other applications */ - SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); + if (!ctx->m_bInputOnly) { + /* Restore simple input mode for other applications */ + SetInputMode(ctx, k_eSwitchInputReportIDs_SimpleControllerState); + } + + SDL_DelHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS, + SDL_GameControllerButtonReportingHintChanged, ctx); SDL_free(context); }