SDL: Added support for the Nintendo Switch Joy-Con Charging Grip

From 29cdb2c9c921e085495b423479c9bf52ed9c5af0 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 3 Aug 2022 18:01:10 -0700
Subject: [PATCH] Added support for the Nintendo Switch Joy-Con Charging Grip

---
 src/joystick/SDL_gamecontroller.c        |  6 +++-
 src/joystick/SDL_joystick.c              | 16 ++++++++--
 src/joystick/SDL_joystick_c.h            |  2 ++
 src/joystick/hidapi/SDL_hidapi_switch.c  | 37 +++++++++++++++++++-----
 src/joystick/hidapi/SDL_hidapijoystick.c | 10 +++++--
 5 files changed, 57 insertions(+), 14 deletions(-)

diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index 626f69174d1..3c782a1f696 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -594,7 +594,8 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI
     } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) {
         SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,lefttrigger:a4,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,", sizeof(mapping_string));
     } else if (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product) ||
-               SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product)) {
+               SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product) ||
+               SDL_IsJoystickNintendoSwitchJoyConGrip(vendor, product)) {
         switch (guid.data[15]) {
         case 9:
         case 10:
@@ -619,6 +620,9 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI
         } else if (SDL_IsJoystickSteamController(vendor, product)) {
             /* Steam controllers have 2 back paddle buttons */
             SDL_strlcat(mapping_string, "paddle1:b16,paddle2:b15,", sizeof(mapping_string));
+        } else if (SDL_IsJoystickNintendoSwitchJoyConPair(vendor, product)) {
+            /* The Nintendo Switch Joy-Con combined controller has a share button */
+            SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string));
         } else {
             switch (SDL_GetJoystickGameControllerTypeFromGUID(guid, NULL)) {
             case SDL_CONTROLLER_TYPE_PS4:
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index d6f7e0c7ed1..931aa9e46e4 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -2110,9 +2110,7 @@ SDL_IsJoystickNintendoSwitchPro(Uint16 vendor_id, Uint16 product_id)
 {
     EControllerType eType = GuessControllerType(vendor_id, product_id);
     return (eType == k_eControllerType_SwitchProController ||
-            eType == k_eControllerType_SwitchInputOnlyController ||
-            eType == k_eControllerType_SwitchJoyConPair ||
-            (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP));
+            eType == k_eControllerType_SwitchInputOnlyController);
 }
 
 SDL_bool
@@ -2144,6 +2142,18 @@ SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id)
     return (eType == k_eControllerType_SwitchJoyConRight);
 }
 
+SDL_bool
+SDL_IsJoystickNintendoSwitchJoyConGrip(Uint16 vendor_id, Uint16 product_id)
+{
+    return (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP);
+}
+
+SDL_bool
+SDL_IsJoystickNintendoSwitchJoyConPair(Uint16 vendor_id, Uint16 product_id)
+{
+    return (vendor_id == USB_VENDOR_NINTENDO && product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_PAIR);
+}
+
 SDL_bool
 SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id)
 {
diff --git a/src/joystick/SDL_joystick_c.h b/src/joystick/SDL_joystick_c.h
index 0e50758ae87..db630c81721 100644
--- a/src/joystick/SDL_joystick_c.h
+++ b/src/joystick/SDL_joystick_c.h
@@ -90,6 +90,8 @@ extern SDL_bool SDL_IsJoystickNintendoSwitchProInputOnly(Uint16 vendor_id, Uint1
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyCon(Uint16 vendor_id, Uint16 product_id);
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConLeft(Uint16 vendor_id, Uint16 product_id);
 extern SDL_bool SDL_IsJoystickNintendoSwitchJoyConRight(Uint16 vendor_id, Uint16 product_id);
+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 Steam Controller */
 extern SDL_bool SDL_IsJoystickSteamController(Uint16 vendor_id, Uint16 product_id);
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index d1271f13485..4ff8ecaeb66 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -384,7 +384,8 @@ HIDAPI_DriverJoyCons_IsSupportedDevice(const char *name, SDL_GameControllerType
 {
     if (vendor_id == USB_VENDOR_NINTENDO) {
         if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_LEFT ||
-            product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT) {
+            product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT ||
+            product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP) {
             return SDL_TRUE;
         }
     }
@@ -410,11 +411,6 @@ HIDAPI_DriverSwitch_IsSupportedDevice(const char *name, SDL_GameControllerType t
         return SDL_FALSE;
     }
 
-    if (vendor_id == USB_VENDOR_NINTENDO) {
-        if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP) {
-            return SDL_TRUE;
-        }
-    }
     return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) ? SDL_TRUE : SDL_FALSE;
 }
 
@@ -1101,9 +1097,20 @@ HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
 {
     /* The NES controllers need additional fix up, since we can't detect them without opening the device */
     if (device->vendor_id == USB_VENDOR_NINTENDO &&
-        device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT) {
+        (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT ||
+         device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP)) {
         ESwitchDeviceInfoControllerType eControllerType = ReadJoyConControllerType(device);
         switch (eControllerType) {
+        case k_eSwitchDeviceInfoControllerType_JoyConLeft:
+            SDL_free(device->name);
+            device->name = SDL_strdup("Nintendo Switch Joy-Con (L)");
+            device->guid.data[15] = eControllerType;
+            break;
+        case k_eSwitchDeviceInfoControllerType_JoyConRight:
+            SDL_free(device->name);
+            device->name = SDL_strdup("Nintendo Switch Joy-Con (R)");
+            device->guid.data[15] = eControllerType;
+            break;
         case k_eSwitchDeviceInfoControllerType_NESLeft:
             SDL_free(device->name);
             device->name = SDL_strdup("NES Controller (L)");
@@ -1114,6 +1121,19 @@ HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device)
             device->name = SDL_strdup("NES Controller (R)");
             device->guid.data[15] = eControllerType;
             break;
+        case k_eSwitchDeviceInfoControllerType_Unknown:
+            if (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP) {
+                if (device->interface_number == 1) {
+                    SDL_free(device->name);
+                    device->name = SDL_strdup("Nintendo Switch Joy-Con (L)");
+                    device->guid.data[15] = k_eSwitchDeviceInfoControllerType_JoyConLeft;
+                } else {
+                    SDL_free(device->name);
+                    device->name = SDL_strdup("Nintendo Switch Joy-Con (R)");
+                    device->guid.data[15] = k_eSwitchDeviceInfoControllerType_JoyConRight;
+                }
+            }
+            break;
         default:
             break;
         }
@@ -1930,7 +1950,8 @@ HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device)
         }
     }
 
-    if (!ctx->m_bInputOnly && !ctx->m_bUsingBluetooth) {
+    if (!ctx->m_bInputOnly && !ctx->m_bUsingBluetooth &&
+        ctx->device->product_id != USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP) {
         const Uint32 INPUT_WAIT_TIMEOUT_MS = 100;
         if (SDL_TICKS_PASSED(now, ctx->m_unLastInput + INPUT_WAIT_TIMEOUT_MS)) {
             /* Steam may have put the controller back into non-reporting mode */
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index 188dac0dab0..4a9b80fd3f9 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -746,10 +746,16 @@ HIDAPI_CreateCombinedJoyCons()
             continue;
         }
 
-        if (!joycons[0] && SDL_IsJoystickNintendoSwitchJoyConLeft(device->vendor_id, device->product_id)) {
+        if (!joycons[0] &&
+            (SDL_IsJoystickNintendoSwitchJoyConLeft(device->vendor_id, device->product_id) ||
+             (SDL_IsJoystickNintendoSwitchJoyConGrip(device->vendor_id, device->product_id) &&
+              SDL_strstr(device->name, "(L)") != NULL))) {
             joycons[0] = device;
         }
-        if (!joycons[1] && SDL_IsJoystickNintendoSwitchJoyConRight(device->vendor_id, device->product_id)) {
+        if (!joycons[1] &&
+            (SDL_IsJoystickNintendoSwitchJoyConRight(device->vendor_id, device->product_id) ||
+             (SDL_IsJoystickNintendoSwitchJoyConGrip(device->vendor_id, device->product_id) &&
+              SDL_strstr(device->name, "(R)") != NULL))) {
             joycons[1] = device;
         }
         if (joycons[0] && joycons[1]) {