SDL: Added support for the Nintendo Online controllers

From 68544be44bfa26d65726c74bd01c6fc79f4641f1 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Fri, 10 Jun 2022 11:31:59 -0700
Subject: [PATCH] Added support for the Nintendo Online controllers

---
 src/joystick/SDL_gamecontroller.c          |  6 +++
 src/joystick/SDL_joystick.c                |  9 +++++
 src/joystick/controller_type.c             |  3 ++
 src/joystick/hidapi/SDL_hidapi_gamecube.c  |  2 +-
 src/joystick/hidapi/SDL_hidapi_luna.c      |  2 +-
 src/joystick/hidapi/SDL_hidapi_ps4.c       |  2 +-
 src/joystick/hidapi/SDL_hidapi_ps5.c       |  2 +-
 src/joystick/hidapi/SDL_hidapi_stadia.c    |  2 +-
 src/joystick/hidapi/SDL_hidapi_steam.c     |  2 +-
 src/joystick/hidapi/SDL_hidapi_switch.c    | 46 ++++++++++++++++++++--
 src/joystick/hidapi/SDL_hidapi_xbox360.c   |  2 +-
 src/joystick/hidapi/SDL_hidapi_xbox360w.c  |  2 +-
 src/joystick/hidapi/SDL_hidapi_xboxone.c   |  2 +-
 src/joystick/hidapi/SDL_hidapijoystick.c   |  4 +-
 src/joystick/hidapi/SDL_hidapijoystick_c.h |  2 +-
 src/joystick/usb_ids.h                     |  3 ++
 16 files changed, 76 insertions(+), 15 deletions(-)

diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c
index 97aeaa73edc..2472695a84e 100644
--- a/src/joystick/SDL_gamecontroller.c
+++ b/src/joystick/SDL_gamecontroller.c
@@ -587,6 +587,12 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI
         (vendor == USB_VENDOR_SHENZHEN && product == USB_PRODUCT_EVORETRO_GAMECUBE_ADAPTER)) {
         /* GameCube driver has 12 buttons and 6 axes */
         SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b6,dpleft:b4,dpright:b5,dpup:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b9,righttrigger:a5,rightx:a2,righty:a3,start:b8,x:b2,y:b3,", sizeof(mapping_string));
+    } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {
+        SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,righttrigger:a5,start:b6,x:b2,y:b3,misc1:b15,", sizeof(mapping_string));
+    } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) {
+        SDL_strlcat(mapping_string, "a:b0,b:b1,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,rightshoulder:b10,righttrigger:a5,start:b6,misc1:b15,", sizeof(mapping_string));
+    } 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 {
         /* All other controllers have the standard set of 19 buttons and 6 axes */
         SDL_strlcat(mapping_string, "a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a2,righty:a3,start:b6,x:b2,y:b3,", sizeof(mapping_string));
diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c
index 2d31578e8c3..e96de4cc03a 100644
--- a/src/joystick/SDL_joystick.c
+++ b/src/joystick/SDL_joystick.c
@@ -1810,6 +1810,11 @@ SDL_CreateJoystickName(Uint16 vendor, Uint16 product, const char *vendor_name, c
     char *name;
     size_t i, len;
 
+    /* Use the given name for the Nintendo Online NES Controllers */
+    if (SDL_strncmp(product_name, "NES Controller", 14) == 0) {
+        return SDL_strdup(product_name);
+    }
+
     custom_name = GuessControllerName(vendor, product);
     if (custom_name) {
         return SDL_strdup(custom_name);
@@ -2051,6 +2056,10 @@ SDL_GetJoystickGameControllerType(const char *name, Uint16 vendor, Uint16 produc
                 break;
             case k_eControllerType_SwitchJoyConLeft:
             case k_eControllerType_SwitchJoyConRight:
+                /* We always support the Nintendo Online NES Controllers */
+                if (name && SDL_strncmp(name, "NES Controller", 14) == 0) {
+                    return SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO;
+                }
                 type = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_FALSE) ? SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO : SDL_CONTROLLER_TYPE_UNKNOWN;
                 break;
             default:
diff --git a/src/joystick/controller_type.c b/src/joystick/controller_type.c
index 45648e4e27e..187e9c47bb3 100644
--- a/src/joystick/controller_type.c
+++ b/src/joystick/controller_type.c
@@ -531,6 +531,9 @@ static const ControllerDescription_t arrControllers[] = {
     // * ZhiXu Gamepad Wireless
     // * Sunwaytek Wireless Motion Controller for Nintendo Switch
 	{ MAKE_CONTROLLER_ID( 0x057e, 0x2009 ), k_eControllerType_SwitchProController, NULL },        // Nintendo Switch Pro Controller
+    { MAKE_CONTROLLER_ID( 0x057e, 0x2017 ), k_eControllerType_SwitchProController, NULL },        // Nintendo Online SNES Controller
+    { MAKE_CONTROLLER_ID( 0x057e, 0x2019 ), k_eControllerType_SwitchProController, NULL },        // Nintendo Online N64 Controller
+    { MAKE_CONTROLLER_ID( 0x057e, 0x201e ), k_eControllerType_SwitchProController, NULL },        // Nintendo Online SEGA Genesis Controller
     
 	{ MAKE_CONTROLLER_ID( 0x0f0d, 0x00c1 ), k_eControllerType_SwitchInputOnlyController, NULL },  // HORIPAD for Nintendo Switch
 	{ MAKE_CONTROLLER_ID( 0x0f0d, 0x0092 ), k_eControllerType_SwitchInputOnlyController, NULL },  // HORI Pokken Tournament DX Pro Pad
diff --git a/src/joystick/hidapi/SDL_hidapi_gamecube.c b/src/joystick/hidapi/SDL_hidapi_gamecube.c
index 694556482e8..dad4daeaabb 100644
--- a/src/joystick/hidapi/SDL_hidapi_gamecube.c
+++ b/src/joystick/hidapi/SDL_hidapi_gamecube.c
@@ -71,7 +71,7 @@ HIDAPI_DriverGameCube_IsSupportedDevice(const char *name, SDL_GameControllerType
 }
 
 static const char *
-HIDAPI_DriverGameCube_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverGameCube_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return "Nintendo GameCube Controller";
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_luna.c b/src/joystick/hidapi/SDL_hidapi_luna.c
index bf497d224d1..4b283e28407 100644
--- a/src/joystick/hidapi/SDL_hidapi_luna.c
+++ b/src/joystick/hidapi/SDL_hidapi_luna.c
@@ -54,7 +54,7 @@ HIDAPI_DriverLuna_IsSupportedDevice(const char *name, SDL_GameControllerType typ
 }
 
 static const char *
-HIDAPI_DriverLuna_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverLuna_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return "Amazon Luna Controller";
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c
index c11dfb364c7..7c87043cfbf 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps4.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps4.c
@@ -155,7 +155,7 @@ HIDAPI_DriverPS4_IsSupportedDevice(const char *name, SDL_GameControllerType type
 }
 
 static const char *
-HIDAPI_DriverPS4_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverPS4_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     if (vendor_id == USB_VENDOR_SONY) {
         return "PS4 Controller";
diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c
index 3f1b62cc4c4..ed9ab788664 100644
--- a/src/joystick/hidapi/SDL_hidapi_ps5.c
+++ b/src/joystick/hidapi/SDL_hidapi_ps5.c
@@ -185,7 +185,7 @@ HIDAPI_DriverPS5_IsSupportedDevice(const char *name, SDL_GameControllerType type
 }
 
 static const char *
-HIDAPI_DriverPS5_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverPS5_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     if (vendor_id == USB_VENDOR_SONY) {
         return "PS5 Controller";
diff --git a/src/joystick/hidapi/SDL_hidapi_stadia.c b/src/joystick/hidapi/SDL_hidapi_stadia.c
index 0a5c2584f23..35025db9ebe 100644
--- a/src/joystick/hidapi/SDL_hidapi_stadia.c
+++ b/src/joystick/hidapi/SDL_hidapi_stadia.c
@@ -55,7 +55,7 @@ HIDAPI_DriverStadia_IsSupportedDevice(const char *name, SDL_GameControllerType t
 }
 
 static const char *
-HIDAPI_DriverStadia_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverStadia_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return "Google Stadia Controller";
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_steam.c b/src/joystick/hidapi/SDL_hidapi_steam.c
index b69c598dc04..bbf1f0e2b81 100644
--- a/src/joystick/hidapi/SDL_hidapi_steam.c
+++ b/src/joystick/hidapi/SDL_hidapi_steam.c
@@ -1004,7 +1004,7 @@ HIDAPI_DriverSteam_IsSupportedDevice(const char *name, SDL_GameControllerType ty
 }
 
 static const char *
-HIDAPI_DriverSteam_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverSteam_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return "Steam Controller";
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c
index 13b5e29d0f9..3fbd91e0266 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch.c
@@ -305,9 +305,29 @@ HasHomeLED(int vendor_id, int product_id)
         return SDL_FALSE;
     }
 
+    /* The Nintendo Online classic controllers don't have a Home LED */
+    if (vendor_id == USB_VENDOR_NINTENDO &&
+        (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER ||
+         product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER ||
+         product_id == USB_PRODUCT_NINTENDO_SNES_CONTROLLER)) {
+        return SDL_FALSE;
+    }
+
     return SDL_TRUE;
 }
 
+static SDL_bool
+AlwaysUsesLabels(int vendor_id, int product_id)
+{
+    /* These controllers don't have a diamond button configuration, so always use labels */
+    if (vendor_id == USB_VENDOR_NINTENDO &&
+        (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER ||
+         product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER)) {
+        return SDL_TRUE;
+    }
+    return SDL_FALSE;
+}
+
 static SDL_bool
 IsGameCubeFormFactor(int vendor_id, int product_id)
 {
@@ -342,7 +362,7 @@ HIDAPI_DriverSwitch_IsSupportedDevice(const char *name, SDL_GameControllerType t
 }
 
 static const char *
-HIDAPI_DriverSwitch_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverSwitch_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     /* Give a user friendly name for this controller */
     if (vendor_id == USB_VENDOR_NINTENDO) {
@@ -355,8 +375,24 @@ HIDAPI_DriverSwitch_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
         }
 
         if (product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT) {
+            /* Use the given name for the Nintendo Online NES Controllers */
+            if (SDL_strncmp(name, "NES Controller", 14) == 0) {
+                return name;
+            }
             return "Nintendo Switch Joy-Con Right";
         }
+
+        if (product_id == USB_PRODUCT_NINTENDO_N64_CONTROLLER) {
+            return "Nintendo N64 Controller";
+        }
+
+        if (product_id == USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER) {
+            return "Nintendo SEGA Genesis Controller";
+        }
+
+        if (product_id == USB_PRODUCT_NINTENDO_SNES_CONTROLLER) {
+            return "Nintendo SNES Controller";
+        }
     }
 
     return "Nintendo Switch Pro Controller";
@@ -1061,8 +1097,12 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti
         ctx->m_bIsGameCube = SDL_TRUE;
     }
 
-    SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
-                        SDL_GameControllerButtonReportingHintChanged, ctx);
+    if (AlwaysUsesLabels(device->vendor_id, device->product_id)) {
+        ctx->m_bUseButtonLabels = SDL_TRUE;
+    } else {
+        SDL_AddHintCallback(SDL_HINT_GAMECONTROLLER_USE_BUTTON_LABELS,
+                            SDL_GameControllerButtonReportingHintChanged, ctx);
+    }
 
     /* Initialize the joystick capabilities */
     if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft ||
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360.c b/src/joystick/hidapi/SDL_hidapi_xbox360.c
index da7aa240f69..28e04cfa4f5 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c
@@ -83,7 +83,7 @@ HIDAPI_DriverXbox360_IsSupportedDevice(const char *name, SDL_GameControllerType
 }
 
 static const char *
-HIDAPI_DriverXbox360_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverXbox360_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return NULL;
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_xbox360w.c b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
index 91c15a73b1e..0c7dfef938a 100644
--- a/src/joystick/hidapi/SDL_hidapi_xbox360w.c
+++ b/src/joystick/hidapi/SDL_hidapi_xbox360w.c
@@ -57,7 +57,7 @@ HIDAPI_DriverXbox360W_IsSupportedDevice(const char *name, SDL_GameControllerType
 }
 
 static const char *
-HIDAPI_DriverXbox360W_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverXbox360W_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return "Xbox 360 Wireless Controller";
 }
diff --git a/src/joystick/hidapi/SDL_hidapi_xboxone.c b/src/joystick/hidapi/SDL_hidapi_xboxone.c
index 1f0cf5b5a85..b549ef6d672 100644
--- a/src/joystick/hidapi/SDL_hidapi_xboxone.c
+++ b/src/joystick/hidapi/SDL_hidapi_xboxone.c
@@ -285,7 +285,7 @@ HIDAPI_DriverXboxOne_IsSupportedDevice(const char *name, SDL_GameControllerType
 }
 
 static const char *
-HIDAPI_DriverXboxOne_GetDeviceName(Uint16 vendor_id, Uint16 product_id)
+HIDAPI_DriverXboxOne_GetDeviceName(const char *name, Uint16 vendor_id, Uint16 product_id)
 {
     return NULL;
 }
diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c
index a72e6a2cd72..0c5dc1ee4b1 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick.c
+++ b/src/joystick/hidapi/SDL_hidapijoystick.c
@@ -200,8 +200,8 @@ HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device)
 
     device->driver = HIDAPI_GetDeviceDriver(device);
     if (device->driver) {
-        const char *name = device->driver->GetDeviceName(device->vendor_id, device->product_id);
-        if (name) {
+        const char *name = device->driver->GetDeviceName(device->name, device->vendor_id, device->product_id);
+        if (name && name != device->name) {
             SDL_free(device->name);
             device->name = SDL_strdup(name);
         }
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index c1b75d8db5e..73c2ad92249 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -90,7 +90,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver
     SDL_bool enabled;
     SDL_bool enabled_default;
     SDL_bool (*IsSupportedDevice)(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol);
-    const char *(*GetDeviceName)(Uint16 vendor_id, Uint16 product_id);
+    const char *(*GetDeviceName)(const char *name, Uint16 vendor_id, Uint16 product_id);
     SDL_bool (*InitDevice)(SDL_HIDAPI_Device *device);
     int (*GetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id);
     void (*SetDevicePlayerIndex)(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index);
diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h
index 97f37b0f8a3..14c84e5df07 100644
--- a/src/joystick/usb_ids.h
+++ b/src/joystick/usb_ids.h
@@ -47,6 +47,9 @@
 #define USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_LEFT            0x2006
 #define USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT           0x2007
 #define USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP            0x200e
+#define USB_PRODUCT_NINTENDO_N64_CONTROLLER                 0x2019
+#define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER        0x201e
+#define USB_PRODUCT_NINTENDO_SNES_CONTROLLER                0x2017
 #define USB_PRODUCT_RAZER_PANTHERA                          0x0401
 #define USB_PRODUCT_RAZER_PANTHERA_EVO                      0x1008
 #define USB_PRODUCT_RAZER_ATROX                             0x0a00