SDL: Use the standard gamepad type for Switch Pro controllers using the GameCube form factor

From d98e1bdfe1f91ef4453912997b379047bedc3f43 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 10 Nov 2023 16:11:10 -0800
Subject: [PATCH] Use the standard gamepad type for Switch Pro controllers
 using the GameCube form factor

---
 src/joystick/SDL_gamepad.c              |  40 +++---
 src/joystick/SDL_joystick.c             |  21 +++
 src/joystick/SDL_joystick_c.h           |   3 +
 src/joystick/controller_list.h          |   2 +-
 src/joystick/hidapi/SDL_hidapi_switch.c | 177 +++++++++++-------------
 5 files changed, 121 insertions(+), 122 deletions(-)

diff --git a/src/joystick/SDL_gamepad.c b/src/joystick/SDL_gamepad.c
index 9db1c31c2db2..fabec016f5b1 100644
--- a/src/joystick/SDL_gamepad.c
+++ b/src/joystick/SDL_gamepad.c
@@ -798,31 +798,23 @@ static GamepadMapping_t *SDL_CreateMappingForHIDAPIGamepad(SDL_JoystickGUID guid
                 /* The original SHIELD controller has a touchpad as well */
                 SDL_strlcat(mapping_string, "touchpad:b16,", sizeof(mapping_string));
             }
-        } else {
-            switch (SDL_GetGamepadTypeFromGUID(guid, NULL)) {
-            case SDL_GAMEPAD_TYPE_PS4:
-                /* PS4 controllers have an additional touchpad button */
-                SDL_strlcat(mapping_string, "touchpad:b15,", sizeof(mapping_string));
-                break;
-            case SDL_GAMEPAD_TYPE_PS5:
-                /* PS5 controllers have a microphone button and an additional touchpad button */
-                SDL_strlcat(mapping_string, "touchpad:b15,misc1:b16,", sizeof(mapping_string));
-                /* DualSense Edge controllers have paddles */
-                if (SDL_IsJoystickDualSenseEdge(vendor, product)) {
-                    SDL_strlcat(mapping_string, "paddle1:b20,paddle2:b19,paddle3:b18,paddle4:b17,", sizeof(mapping_string));
-                }
-                break;
-            case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO:
-                /* Nintendo Switch Pro controllers have a screenshot button */
-                SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string));
-                break;
-            default:
-                if (vendor == 0 && product == 0) {
-                    /* This is a Bluetooth Nintendo Switch Pro controller */
-                    SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string));
-                }
-                break;
+        } else if (SDL_IsJoystickPS4(vendor, product)) {
+            /* PS4 controllers have an additional touchpad button */
+            SDL_strlcat(mapping_string, "touchpad:b15,", sizeof(mapping_string));
+        } else if (SDL_IsJoystickPS5(vendor, product)) {
+            /* PS5 controllers have a microphone button and an additional touchpad button */
+            SDL_strlcat(mapping_string, "touchpad:b15,misc1:b16,", sizeof(mapping_string));
+            /* DualSense Edge controllers have paddles */
+            if (SDL_IsJoystickDualSenseEdge(vendor, product)) {
+                SDL_strlcat(mapping_string, "paddle1:b20,paddle2:b19,paddle3:b18,paddle4:b17,", sizeof(mapping_string));
             }
+        } else if (SDL_IsJoystickNintendoSwitchPro(vendor, product) ||
+                   SDL_IsJoystickNintendoSwitchProInputOnly(vendor, product)) {
+            /* Nintendo Switch Pro controllers have a screenshot button */
+            SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string));
+        } else if (vendor == 0 && product == 0) {
+            /* This is a Bluetooth Nintendo Switch Pro controller */
+            SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string));
         }
     }
 
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 3d030e179bfa..14d0a39833fa 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -2368,6 +2368,10 @@ SDL_GamepadType SDL_GetGamepadTypeFromVIDPID(Uint16 vendor, Uint16 product, cons
     } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR) {
         type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR;
 
+    } else if (forUI && SDL_IsJoystickGameCube(vendor, product)) {
+        /* We don't have a type for the Nintendo GameCube controller */
+        type = SDL_GAMEPAD_TYPE_STANDARD;
+
     } else {
         switch (GuessControllerType(vendor, product)) {
         case k_eControllerType_XBox360Controller:
@@ -2595,6 +2599,23 @@ SDL_bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product
     return vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
 }
 
+SDL_bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id)
+{
+    static Uint32 gamecube_formfactor[] = {
+        MAKE_VIDPID(0x0e6f, 0x0185), /* PDP Wired Fight Pad Pro for Nintendo Switch */
+        MAKE_VIDPID(0x20d6, 0xa711), /* PowerA Wired Controller Nintendo GameCube Style */
+    };
+    Uint32 id = MAKE_VIDPID(vendor_id, product_id);
+    int i;
+
+    for (i = 0; i < SDL_arraysize(gamecube_formfactor); ++i) {
+        if (id == gamecube_formfactor[i]) {
+            return SDL_TRUE;
+        }
+    }
+    return SDL_FALSE;
+}
+
 SDL_bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id)
 {
     return ((vendor_id == USB_VENDOR_AMAZON && product_id == USB_PRODUCT_AMAZON_LUNA_CONTROLLER) ||
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index d81896e28e6b..8b37204d5a8e 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -108,6 +108,9 @@ extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id);
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id);
 
+/* Function to return whether a joystick is a Nintendo GameCube style controller */
+extern SDL_bool SDL_IsJoystickGameCube(Uint16 vendor_id, Uint16 product_id);
+
 /* Function to return whether a joystick is an Amazon Luna controller */
 extern SDL_bool SDL_IsJoystickAmazonLunaController(Uint16 vendor_id, Uint16 product_id);
 
diff --git a/src/joystick/controller_list.h b/src/joystick/controller_list.h
index ffb5a46bde5a..331bf171588e 100644
--- a/src/joystick/controller_list.h
+++ b/src/joystick/controller_list.h
@@ -567,7 +567,7 @@ static const ControllerDescription_t arrControllers[] = {
 	// The first two, at least, shouldn't have their buttons remapped, and since we
 	// can't tell which model we're actually using, we won't do any button remapping
 	// for any of them.
-	{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00dc ), k_eControllerType_XInputSwitchController, NULL },	 // HORIPAD S - Looks like a Switch controller but uses the Xbox 360 controller protocol
+	{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00dc ), k_eControllerType_XInputSwitchController, NULL },	 // HORIPAD S - Looks like a Switch controller but uses the Xbox 360 controller protocol, there is also a version of this that looks like a GameCube controller
 	{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0180 ), k_eControllerType_SwitchInputOnlyController, NULL },  // PDP Faceoff Wired Pro Controller for Nintendo Switch
 	{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0181 ), k_eControllerType_SwitchInputOnlyController, NULL },  // PDP Faceoff Deluxe Wired Pro Controller for Nintendo Switch
 	{ MAKE_CONTROLLER_ID( 0x0e6f, 0x0184 ), k_eControllerType_SwitchInputOnlyController, NULL },  // PDP Faceoff Wired Deluxe+ Audio Controller
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index e9ad734d9adc..46ffc29da3b0 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -263,7 +263,6 @@ typedef struct
     SDL_HIDAPI_Device *device;
     SDL_Joystick *joystick;
     SDL_bool m_bInputOnly;
-    SDL_bool m_bIsGameCube;
     SDL_bool m_bUseButtonLabels;
     SDL_bool m_bPlayerLights;
     int m_nPlayerIndex;
@@ -909,8 +908,7 @@ static Sint16 ApplyStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, i
         ctx->m_StickExtents[nStick].axis[nAxis].sMin = sRawValue;
     }
 
-    return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax,
-                                   SDL_MIN_SINT16, SDL_MAX_SINT16);
+    return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_StickExtents[nStick].axis[nAxis].sMin, ctx->m_StickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16);
 }
 
 static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nStick, int nAxis, Sint16 sRawValue)
@@ -927,8 +925,7 @@ static Sint16 ApplySimpleStickCalibration(SDL_DriverSwitch_Context *ctx, int nSt
         ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin = sRawValue;
     }
 
-    return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax,
-                                   SDL_MIN_SINT16, SDL_MAX_SINT16);
+    return (Sint16)HIDAPI_RemapVal(sRawValue, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMin, ctx->m_SimpleStickExtents[nStick].axis[nAxis].sMax, SDL_MIN_SINT16, SDL_MAX_SINT16);
 }
 
 static Uint8 RemapButton(SDL_DriverSwitch_Context *ctx, Uint8 button)
@@ -1034,7 +1031,10 @@ static SDL_bool HasHomeLED(SDL_DriverSwitch_Context *ctx)
 
 static SDL_bool AlwaysUsesLabels(int vendor_id, int product_id, ESwitchDeviceInfoControllerType eControllerType)
 {
-    /* These controllers don't have a diamond button configuration, so always use labels */
+    /* Some controllers don't have a diamond button configuration, so should always use labels */
+    if (SDL_IsJoystickGameCube(vendor_id, product_id)) {
+        return SDL_TRUE;
+    }
     switch (eControllerType) {
     case k_eSwitchDeviceInfoControllerType_HVCLeft:
     case k_eSwitchDeviceInfoControllerType_HVCRight:
@@ -1048,25 +1048,6 @@ static SDL_bool AlwaysUsesLabels(int vendor_id, int product_id, ESwitchDeviceInf
     }
 }
 
-static SDL_bool IsGameCubeFormFactor(int vendor_id, int product_id)
-{
-#if 0
-    static Uint32 gamecube_formfactor[] = {
-        MAKE_VIDPID(0x0e6f, 0x0185), /* PDP Wired Fight Pad Pro for Nintendo Switch */
-        MAKE_VIDPID(0x20d6, 0xa711), /* Core (Plus) Wired Controller */
-    };
-    Uint32 id = MAKE_VIDPID(vendor_id, product_id);
-    int i;
-
-    for (i = 0; i < SDL_arraysize(gamecube_formfactor); ++i) {
-        if (id == gamecube_formfactor[i]) {
-            return SDL_TRUE;
-        }
-    }
-#endif
-    return SDL_FALSE;
-}
-
 static void HIDAPI_DriverNintendoClassic_RegisterHints(SDL_HintCallback callback, void *userdata)
 {
     SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_NINTENDO_CLASSIC, callback, userdata);
@@ -1183,73 +1164,80 @@ static SDL_bool HIDAPI_DriverSwitch_IsSupportedDevice(SDL_HIDAPI_Device *device,
 static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
 {
     SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context;
-    char serial[18];
-
-    switch (ctx->m_eControllerType) {
-    case k_eSwitchDeviceInfoControllerType_JoyConLeft:
-        HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)");
-        HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT);
-        device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
-        break;
-    case k_eSwitchDeviceInfoControllerType_JoyConRight:
-        HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)");
-        HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT);
-        device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
-        break;
-    case k_eSwitchDeviceInfoControllerType_ProController:
-    case k_eSwitchDeviceInfoControllerType_LicProController:
-        HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");
-        HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO);
-        device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
-        break;
-    case k_eSwitchDeviceInfoControllerType_HVCLeft:
-        HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)");
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_HVCRight:
-        HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)");
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_NESLeft:
-        HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)");
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_NESRight:
-        HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)");
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_SNES:
-        HIDAPI_SetDeviceName(device, "Nintendo SNES Controller");
-        HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER);
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_N64:
-        HIDAPI_SetDeviceName(device, "Nintendo N64 Controller");
-        HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER);
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
-        HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller");
-        HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER);
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    case k_eSwitchDeviceInfoControllerType_Unknown:
-        /* We couldn't read the device info for this controller, might not be fully compliant */
-        return;
-    default:
-        device->type = SDL_GAMEPAD_TYPE_STANDARD;
-        break;
-    }
-    device->guid.data[15] = ctx->m_eControllerType;
 
-    (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
-                       ctx->m_rgucMACAddress[0],
-                       ctx->m_rgucMACAddress[1],
-                       ctx->m_rgucMACAddress[2],
-                       ctx->m_rgucMACAddress[3],
-                       ctx->m_rgucMACAddress[4],
-                       ctx->m_rgucMACAddress[5]);
-    HIDAPI_SetDeviceSerial(device, serial);
+    if (ctx->m_bInputOnly) {
+        if (SDL_IsJoystickGameCube(device->vendor_id, device->product_id)) {
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+        }
+    } else {
+        char serial[18];
+
+        switch (ctx->m_eControllerType) {
+        case k_eSwitchDeviceInfoControllerType_JoyConLeft:
+            HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (L)");
+            HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT);
+            device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT;
+            break;
+        case k_eSwitchDeviceInfoControllerType_JoyConRight:
+            HIDAPI_SetDeviceName(device, "Nintendo Switch Joy-Con (R)");
+            HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT);
+            device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT;
+            break;
+        case k_eSwitchDeviceInfoControllerType_ProController:
+        case k_eSwitchDeviceInfoControllerType_LicProController:
+            HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");
+            HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_PRO);
+            device->type = SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO;
+            break;
+        case k_eSwitchDeviceInfoControllerType_HVCLeft:
+            HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (1)");
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_HVCRight:
+            HIDAPI_SetDeviceName(device, "Nintendo HVC Controller (2)");
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_NESLeft:
+            HIDAPI_SetDeviceName(device, "Nintendo NES Controller (L)");
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_NESRight:
+            HIDAPI_SetDeviceName(device, "Nintendo NES Controller (R)");
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_SNES:
+            HIDAPI_SetDeviceName(device, "Nintendo SNES Controller");
+            HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SNES_CONTROLLER);
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_N64:
+            HIDAPI_SetDeviceName(device, "Nintendo N64 Controller");
+            HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_N64_CONTROLLER);
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_SEGA_Genesis:
+            HIDAPI_SetDeviceName(device, "Nintendo SEGA Genesis Controller");
+            HIDAPI_SetDeviceProduct(device, USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER);
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        case k_eSwitchDeviceInfoControllerType_Unknown:
+            /* We couldn't read the device info for this controller, might not be fully compliant */
+            return;
+        default:
+            device->type = SDL_GAMEPAD_TYPE_STANDARD;
+            break;
+        }
+        device->guid.data[15] = ctx->m_eControllerType;
+
+        (void)SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x",
+                           ctx->m_rgucMACAddress[0],
+                           ctx->m_rgucMACAddress[1],
+                           ctx->m_rgucMACAddress[2],
+                           ctx->m_rgucMACAddress[3],
+                           ctx->m_rgucMACAddress[4],
+                           ctx->m_rgucMACAddress[5]);
+        HIDAPI_SetDeviceSerial(device, serial);
+    }
 }
 
 static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
@@ -1267,11 +1255,6 @@ static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
     ctx->m_nMaxWriteAttempts = GetMaxWriteAttempts(device);
     ctx->m_bSyncWrite = SDL_TRUE;
 
-    if (IsGameCubeFormFactor(device->vendor_id, device->product_id)) {
-        /* This is a controller shaped like a GameCube controller, with a large central A button */
-        ctx->m_bIsGameCube = SDL_TRUE;
-    }
-
     /* Find out whether or not we can send output reports */
     ctx->m_bInputOnly = SDL_IsJoystickNintendoSwitchProInputOnly(device->vendor_id, device->product_id);
     if (!ctx->m_bInputOnly) {
@@ -1280,8 +1263,8 @@ static SDL_bool HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
         SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]);
 
         BReadDeviceInfo(ctx);
-        UpdateDeviceIdentity(device);
     }
+    UpdateDeviceIdentity(device);
 
     /* Prefer the USB device over the Bluetooth device */
     if (device->is_bluetooth) {