From 1f007ad5cd6f8103e8975295e6cfa9659a26cad9 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Sat, 30 Aug 2025 10:37:26 -0700 Subject: [PATCH] Added support for the player LED on Nintendo Switch 2 controllers --- src/joystick/hidapi/SDL_hidapi_switch2.c | 81 ++++++++++++++++++++---- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/src/joystick/hidapi/SDL_hidapi_switch2.c b/src/joystick/hidapi/SDL_hidapi_switch2.c index 22762709aa..13367d4438 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch2.c +++ b/src/joystick/hidapi/SDL_hidapi_switch2.c @@ -85,6 +85,9 @@ typedef struct typedef struct { + SDL_HIDAPI_Device *device; + SDL_Joystick *joystick; + SDL_LibUSBContext *libusb; libusb_device_handle *device_handle; bool interface_claimed; @@ -97,6 +100,9 @@ typedef struct Uint8 left_trigger_max; Uint8 right_trigger_max; + bool player_lights; + int player_index; + bool vertical_mode; Uint8 last_state[USB_PACKET_LENGTH]; } SDL_DriverSwitch2_Context; @@ -198,6 +204,37 @@ static void MapTriggerAxis(Uint64 timestamp, SDL_Joystick *joystick, Uint8 axis, SDL_SendJoystickAxis(timestamp, joystick, axis, mapped_value); } +static bool UpdateSlotLED(SDL_DriverSwitch2_Context *ctx) +{ + unsigned char SET_LED_DATA[] = { + 0x09, 0x91, 0x00, 0x07, 0x00, 0x08, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }; + unsigned char calibration_data[0x50] = {0}; + + if (ctx->player_lights && ctx->player_index >= 0) { + SET_LED_DATA[8] = (1 << (ctx->player_index % 4)); + } + int res = SendBulkData(ctx, SET_LED_DATA, sizeof(SET_LED_DATA)); + if (res < 0) { + return SDL_SetError("Couldn't set LED data: %d\n", res); + } + return (RecvBulkData(ctx, calibration_data, 0x40) > 0); +} + +static void SDLCALL SDL_PlayerLEDHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)userdata; + bool player_lights = SDL_GetStringBoolean(hint, true); + + if (player_lights != ctx->player_lights) { + ctx->player_lights = player_lights; + + UpdateSlotLED(ctx); + HIDAPI_UpdateDeviceProperties(ctx->device); + } +} + static void HIDAPI_DriverSwitch2_RegisterHints(SDL_HintCallback callback, void *userdata) { SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH2, callback, userdata); @@ -300,10 +337,6 @@ static bool HIDAPI_DriverSwitch2_InitUSB(SDL_HIDAPI_Device *device) 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 - }; unsigned char flash_read_command[] = { 0x02, 0x91, 0x00, 0x01, 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00 @@ -316,12 +349,6 @@ static bool HIDAPI_DriverSwitch2_InitUSB(SDL_HIDAPI_Device *device) } RecvBulkData(ctx, calibration_data, 0x40); - res = SendBulkData(ctx, SET_LED_DATA, sizeof(SET_LED_DATA)); - if (res < 0) { - return SDL_SetError("Couldn't set LED data: %d\n", res); - } - RecvBulkData(ctx, calibration_data, 0x40); - flash_read_command[12] = 0x80; res = SendBulkData(ctx, flash_read_command, sizeof(flash_read_command)); if (res < 0) { @@ -377,6 +404,7 @@ static bool HIDAPI_DriverSwitch2_InitDevice(SDL_HIDAPI_Device *device) if (!ctx) { return false; } + ctx->device = device; device->context = ctx; if (device->is_bluetooth) { @@ -410,12 +438,31 @@ static int HIDAPI_DriverSwitch2_GetDevicePlayerIndex(SDL_HIDAPI_Device *device, static void HIDAPI_DriverSwitch2_SetDevicePlayerIndex(SDL_HIDAPI_Device *device, SDL_JoystickID instance_id, int player_index) { + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + if (!ctx->joystick) { + return; + } + + ctx->player_index = player_index; + + UpdateSlotLED(ctx); } static bool HIDAPI_DriverSwitch2_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + ctx->joystick = joystick; + + // Initialize player index (needed for setting LEDs) + ctx->player_index = SDL_GetJoystickPlayerIndex(joystick); + ctx->player_lights = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, true); + UpdateSlotLED(ctx); + + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + // Initialize the joystick capabilities switch (device->product_id) { case USB_PRODUCT_NINTENDO_SWITCH2_GAMECUBE_CONTROLLER: @@ -453,7 +500,13 @@ static bool HIDAPI_DriverSwitch2_RumbleJoystickTriggers(SDL_HIDAPI_Device *devic static Uint32 HIDAPI_DriverSwitch2_GetJoystickCapabilities(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { - return 0; + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + Uint32 result = 0; + + if (ctx->player_lights) { + result |= SDL_JOYSTICK_CAP_PLAYER_LED; + } + return result; } static bool HIDAPI_DriverSwitch2_SetJoystickLED(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, Uint8 red, Uint8 green, Uint8 blue) @@ -886,6 +939,12 @@ static bool HIDAPI_DriverSwitch2_UpdateDevice(SDL_HIDAPI_Device *device) static void HIDAPI_DriverSwitch2_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) { + SDL_DriverSwitch2_Context *ctx = (SDL_DriverSwitch2_Context *)device->context; + + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_PLAYER_LED, + SDL_PlayerLEDHintChanged, ctx); + + ctx->joystick = NULL; } static void HIDAPI_DriverSwitch2_FreeDevice(SDL_HIDAPI_Device *device)