From ac5ccbe3867ca9b1201a030635890b6301016a49 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Wed, 27 Aug 2025 15:50:54 -0700
Subject: [PATCH] Moved Nintendo Switch 2 Controller initialization from hid.c
to SDL_hidapi_switch2.c
---
src/hidapi/libusb/hid.c | 81 ----------
src/joystick/hidapi/SDL_hidapi_switch2.c | 163 ++++++++++++++++++---
src/joystick/hidapi/SDL_hidapijoystick_c.h | 2 +
3 files changed, 148 insertions(+), 98 deletions(-)
diff --git a/src/hidapi/libusb/hid.c b/src/hidapi/libusb/hid.c
index d0e1b5c112636..0c4fc660ba496 100644
--- a/src/hidapi/libusb/hid.c
+++ b/src/hidapi/libusb/hid.c
@@ -1324,82 +1324,6 @@ static void init_xboxone(libusb_device_handle *device_handle, unsigned short idV
}
}
-static bool is_ns2(unsigned short idVendor, unsigned short idProduct)
-{
- if (idVendor == 0x057e) {
- if (idProduct == 0x2069) {
- return true;
- }
- if (idProduct == 0x2073) {
- return true;
- }
- }
- return false;
-}
-
-static void init_ns2(libusb_device_handle *device_handle, const struct libusb_config_descriptor *conf_desc)
-{
- int j, k, l, res;
-
- for (j = 0; j < conf_desc->bNumInterfaces; j++) {
- const struct libusb_interface *intf = &conf_desc->interface[j];
- for (k = 0; k < intf->num_altsetting; k++) {
- const struct libusb_interface_descriptor *intf_desc = &intf->altsetting[k];
- if (intf_desc->bInterfaceNumber == 1) {
- uint8_t endpoint = 0;
- for (l = 0; l < intf_desc->bNumEndpoints; l++) {
- const struct libusb_endpoint_descriptor* ep = &intf_desc->endpoint[l];
- if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK && (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) {
- endpoint = ep->bEndpointAddress;
- break;
- }
- }
-
- if (endpoint) {
- res = libusb_claim_interface(device_handle, intf_desc->bInterfaceNumber);
- if (res < 0) {
- LOG("can't claim interface %d: %d\n", intf_desc->bInterfaceNumber, res);
- continue;
- }
-
- const unsigned char DEFAULT_REPORT_DATA[] = {
- 0x03, 0x91, 0x00, 0x0d, 0x00, 0x08,
- 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
- };
- const unsigned char SET_LED_DATA[] = {
- 0x09, 0x91, 0x00, 0x07, 0x00, 0x08,
- 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
- };
-
- int transferred;
- res = libusb_bulk_transfer(device_handle,
- endpoint,
- (unsigned char*)DEFAULT_REPORT_DATA,
- sizeof(DEFAULT_REPORT_DATA),
- &transferred,
- 1000);
- if (res < 0) {
- LOG("can't set report data: %d\n", res);
- }
-
- res = libusb_bulk_transfer(device_handle,
- endpoint,
- (unsigned char*)SET_LED_DATA,
- sizeof(SET_LED_DATA),
- &transferred,
- 1000);
- if (res < 0) {
- LOG("can't set LED data: %d\n", res);
- }
-
- libusb_release_interface(device_handle, intf_desc->bInterfaceNumber);
- return;
- }
- }
- }
- }
-}
-
static void calculate_device_quirks(hid_device *dev, unsigned short idVendor, unsigned short idProduct)
{
static const int VENDOR_SONY = 0x054c;
@@ -1461,11 +1385,6 @@ static int hidapi_initialize_device(hid_device *dev, const struct libusb_interfa
init_xboxone(dev->device_handle, desc.idVendor, desc.idProduct, conf_desc);
}
- /* Initialize Nintendo Switch 2 controllers */
- if (is_ns2(desc.idVendor, desc.idProduct)) {
- init_ns2(dev->device_handle, conf_desc);
- }
-
/* Store off the string descriptor indexes */
dev->manufacturer_index = desc.iManufacturer;
dev->product_index = desc.iProduct;
diff --git a/src/joystick/hidapi/SDL_hidapi_switch2.c b/src/joystick/hidapi/SDL_hidapi_switch2.c
index 8e0a23a1a0557..3655cc02b15bc 100644
--- a/src/joystick/hidapi/SDL_hidapi_switch2.c
+++ b/src/joystick/hidapi/SDL_hidapi_switch2.c
@@ -26,13 +26,21 @@
#ifdef SDL_JOYSTICK_HIDAPI
#include "../../SDL_hints_c.h"
+#include "../../misc/SDL_libusb.h"
#include "../SDL_sysjoystick.h"
#include "SDL_hidapijoystick_c.h"
-#include "SDL_hidapi_rumble.h"
-#include "SDL_hidapi_nintendo.h"
#ifdef SDL_JOYSTICK_HIDAPI_SWITCH2
+typedef struct
+{
+ SDL_LibUSBContext *libusb;
+ libusb_device_handle *device_handle;
+ bool interface_claimed;
+ Uint8 interface_number;
+ Uint8 bulk_endpoint;
+} SDL_DriverSwitch2_Context;
+
static void HIDAPI_DriverSwitch2_RegisterHints(SDL_HintCallback callback, void *userdata)
{
SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata);
@@ -51,29 +59,138 @@ static bool HIDAPI_DriverSwitch2_IsEnabled(void)
static bool HIDAPI_DriverSwitch2_IsSupportedDevice(SDL_HIDAPI_Device *device, const char *name, SDL_GamepadType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
{
if (vendor_id == USB_VENDOR_NINTENDO) {
- switch (product_id) {
- case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER:
- case USB_PRODUCT_NINTENDO_SWITCH2_PRO:
+ switch (product_id) {
+ case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER:
+ case USB_PRODUCT_NINTENDO_SWITCH2_PRO:
return true;
- }
+ }
}
return false;
}
+static bool HIDAPI_DriverSwitch2_InitBluetooth(SDL_HIDAPI_Device *device)
+{
+ // FIXME: Need to add Bluetooth support
+ return SDL_SetError("Nintendo Switch2 controllers not supported over Bluetooth");
+}
+
+static bool FindBulkOutEndpoint(SDL_LibUSBContext *libusb, libusb_device_handle *handle, Uint8 *bInterfaceNumber, Uint8 *bEndpointAddress)
+{
+ struct libusb_config_descriptor *config;
+ if (libusb->get_config_descriptor(libusb->get_device(handle), 0, &config) != 0) {
+ return false;
+ }
+
+ for (int i = 0; i < config->bNumInterfaces; i++) {
+ const struct libusb_interface *iface = &config->interface[i];
+ for (int j = 0; j < iface->num_altsetting; j++) {
+ const struct libusb_interface_descriptor *altsetting = &iface->altsetting[j];
+ if (altsetting->bInterfaceNumber == 1) {
+ for (int k = 0; k < altsetting->bNumEndpoints; k++) {
+ const struct libusb_endpoint_descriptor *ep = &altsetting->endpoint[k];
+ if ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK) == LIBUSB_TRANSFER_TYPE_BULK && (ep->bEndpointAddress & LIBUSB_ENDPOINT_DIR_MASK) == LIBUSB_ENDPOINT_OUT) {
+
+ *bInterfaceNumber = altsetting->bInterfaceNumber;
+ *bEndpointAddress = ep->bEndpointAddress;
+ libusb->free_config_descriptor(config);
+ return true;
+ }
+ }
+ }
+ }
+ }
+ libusb->free_config_descriptor(config);
+ return false;
+}
+
+static bool HIDAPI_DriverSwitch2_InitUSB(SDL_HIDAPI_Device *device)
+{
+ SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context;
+
+ if (!SDL_InitLibUSB(&ctx->libusb)) {
+ return false;
+ }
+
+ ctx->device_handle = (libusb_device_handle *)SDL_GetPointerProperty(SDL_hid_get_properties(device->dev), SDL_PROP_HIDAPI_LIBUSB_DEVICE_HANDLE_POINTER, NULL);
+ if (!ctx->device_handle) {
+ return SDL_SetError("Couldn't get libusb device handle");
+ }
+
+ if (!FindBulkOutEndpoint(ctx->libusb, ctx->device_handle, &ctx->interface_number, &ctx->bulk_endpoint)) {
+ return SDL_SetError("Couldn't find bulk endpoint");
+ }
+
+ int res = ctx->libusb->claim_interface(ctx->device_handle, ctx->interface_number);
+ if (res < 0) {
+ return SDL_SetError("Couldn't claim interface %d: %d\n", ctx->interface_number, res);
+ }
+ ctx->interface_claimed = true;
+
+ const unsigned char DEFAULT_REPORT_DATA[] = {
+ 0x03, 0x91, 0x00, 0x0d, 0x00, 0x08,
+ 0x00, 0x00, 0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF
+ };
+ const unsigned char SET_LED_DATA[] = {
+ 0x09, 0x91, 0x00, 0x07, 0x00, 0x08,
+ 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
+ };
+
+ int transferred;
+ res = ctx->libusb->bulk_transfer(ctx->device_handle,
+ ctx->bulk_endpoint,
+ (unsigned char *)DEFAULT_REPORT_DATA,
+ sizeof(DEFAULT_REPORT_DATA),
+ &transferred,
+ 1000);
+ if (res < 0) {
+ return SDL_SetError("Couldn't set report data: %d\n", res);
+ }
+
+ res = ctx->libusb->bulk_transfer(ctx->device_handle,
+ ctx->bulk_endpoint,
+ (unsigned char *)SET_LED_DATA,
+ sizeof(SET_LED_DATA),
+ &transferred,
+ 1000);
+ if (res < 0) {
+ return SDL_SetError("Couldn't set LED data: %d\n", res);
+ }
+
+ return true;
+}
+
static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device)
{
- // Sometimes the device handle isn't available during enumeration so we don't get the device name, so set it explicitly
- switch (device->product_id) {
- case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER:
- HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
- break;
- case USB_PRODUCT_NINTENDO_SWITCH2_PRO:
- HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");
- break;
- default:
- break;
- }
+ SDL_DriverSwitch2_Context *ctx;
+
+ ctx = (SDL_DriverSwitch2_Context *)SDL_calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ return false;
+ }
+ device->context = ctx;
+
+ if (device->is_bluetooth) {
+ if (!HIDAPI_DriverSwitch2_InitBluetooth(device)) {
+ return false;
+ }
+ } else {
+ if (!HIDAPI_DriverSwitch2_InitUSB(device)) {
+ return false;
+ }
+ }
+
+ // Sometimes the device handle isn't available during enumeration so we don't get the device name, so set it explicitly
+ switch (device->product_id) {
+ case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER:
+ HIDAPI_SetDeviceName(device, "Nintendo GameCube Controller");
+ break;
+ case USB_PRODUCT_NINTENDO_SWITCH2_PRO:
+ HIDAPI_SetDeviceName(device, "Nintendo Switch Pro Controller");
+ break;
+ default:
+ break;
+ }
return HIDAPI_JoystickConnected(device, NULL);
}
@@ -250,6 +367,18 @@ static void HIDAPI_DriverSwitch2_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Jo
static void HIDAPI_DriverSwitch2_FreeDevice(SDL_HIDAPI_Device *device)
{
+ SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context;
+
+ if (ctx) {
+ if (ctx->interface_claimed) {
+ ctx->libusb->release_interface(ctx->device_handle, ctx->interface_number);
+ ctx->interface_claimed = false;
+ }
+ if (ctx->libusb) {
+ SDL_QuitLibUSB();
+ ctx->libusb = NULL;
+ }
+ }
}
SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverSwitch2 = {
diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h
index 35ded59ba1196..a8962468fb259 100644
--- a/src/joystick/hidapi/SDL_hidapijoystick_c.h
+++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h
@@ -35,7 +35,9 @@
#define SDL_JOYSTICK_HIDAPI_STEAM
#define SDL_JOYSTICK_HIDAPI_STEAMDECK
#define SDL_JOYSTICK_HIDAPI_SWITCH
+#ifdef HAVE_LIBUSB
#define SDL_JOYSTICK_HIDAPI_SWITCH2
+#endif
#define SDL_JOYSTICK_HIDAPI_WII
#define SDL_JOYSTICK_HIDAPI_XBOX360
#define SDL_JOYSTICK_HIDAPI_XBOXONE