diff --git a/Makefile.os2 b/Makefile.os2 index af6f1f0b7..7f591c90b 100644 --- a/Makefile.os2 +++ b/Makefile.os2 @@ -89,7 +89,7 @@ SRCS+= SDL_systimer.c SRCS+= SDL_sysloadso.c SRCS+= SDL_sysfilesystem.c SRCS+= SDL_os2joystick.c SDL_syshaptic.c SDL_sysjoystick.c SDL_virtualjoystick.c -SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c +SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SRCS+= SDL_dummyaudio.c SDL_diskaudio.c SRCS+= SDL_nullvideo.c SDL_nullframebuffer.c SDL_nullevents.c SRCS+= SDL_dummysensor.c diff --git a/Makefile.w32 b/Makefile.w32 index 299567b23..e637df717 100644 --- a/Makefile.w32 +++ b/Makefile.w32 @@ -65,7 +65,7 @@ SRCS+= SDL_systimer.c SRCS+= SDL_sysloadso.c SRCS+= SDL_sysfilesystem.c SRCS+= SDL_syshaptic.c SDL_sysjoystick.c SDL_virtualjoystick.c -SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c +SRCS+= SDL_hidapijoystick.c SDL_hidapi_rumble.c SDL_hidapi_combined.c SDL_hidapi_gamecube.c SDL_hidapi_luna.c SDL_hidapi_ps4.c SDL_hidapi_ps5.c SDL_hidapi_shield.c SDL_hidapi_stadia.c SDL_hidapi_switch.c SDL_hidapi_xbox360.c SDL_hidapi_xbox360w.c SDL_hidapi_xboxone.c SDL_hidapi_steam.c SRCS+= SDL_dummyaudio.c SDL_diskaudio.c SRCS+= SDL_nullvideo.c SDL_nullframebuffer.c SDL_nullevents.c SRCS+= SDL_dummysensor.c diff --git a/VisualC-GDK/SDL/SDL.vcxproj b/VisualC-GDK/SDL/SDL.vcxproj index e5565f802..3a1bd7df2 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj +++ b/VisualC-GDK/SDL/SDL.vcxproj @@ -592,6 +592,7 @@ + diff --git a/VisualC-GDK/SDL/SDL.vcxproj.filters b/VisualC-GDK/SDL/SDL.vcxproj.filters index a6e0f4e3c..ffe8ec14b 100644 --- a/VisualC-GDK/SDL/SDL.vcxproj.filters +++ b/VisualC-GDK/SDL/SDL.vcxproj.filters @@ -1054,7 +1054,7 @@ joystick\dummy - + joystick\hidapi @@ -1087,6 +1087,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi diff --git a/VisualC/SDL/SDL.vcxproj b/VisualC/SDL/SDL.vcxproj index 64c07bac7..e3b50a2cd 100644 --- a/VisualC/SDL/SDL.vcxproj +++ b/VisualC/SDL/SDL.vcxproj @@ -483,6 +483,7 @@ + diff --git a/VisualC/SDL/SDL.vcxproj.filters b/VisualC/SDL/SDL.vcxproj.filters index fc5318a09..5e87e1925 100644 --- a/VisualC/SDL/SDL.vcxproj.filters +++ b/VisualC/SDL/SDL.vcxproj.filters @@ -1047,7 +1047,7 @@ joystick\dummy - + joystick\hidapi @@ -1080,6 +1080,9 @@ joystick\hidapi + + joystick\hidapi + joystick\hidapi diff --git a/WhatsNew.txt b/WhatsNew.txt index c058404db..0ad5698ce 100644 --- a/WhatsNew.txt +++ b/WhatsNew.txt @@ -20,6 +20,9 @@ General: the SDL 2.24.0 stable release. * Added SDL_bsearch() and SDL_utf8strnlen() to the stdlib routines * Added SDL_size_mul_overflow() and SDL_size_add_overflow() for better size overflow protection +* Added support for mini-gamepad mode for Nintendo Joy-Con controllers using the HIDAPI driver +* Added the hint SDL_HINT_JOYSTICK_HIDAPI_SWITCH_COMBINE_JOY_CONS to control whether Joy-Con controllers are automatically merged into a unified gamepad when using the HIDAPI driver. This hint defaults on. +* Removed the hint SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS with the new Joy-Con functionality * Added functions to get the platform dependent name for a joystick or game controller: * SDL_JoystickPathForIndex() * SDL_JoystickPath() diff --git a/include/SDL_hints.h b/include/SDL_hints.h index 873c91664..854d44ceb 100644 --- a/include/SDL_hints.h +++ b/include/SDL_hints.h @@ -652,17 +652,6 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_GAMECUBE_RUMBLE_BRAKE "SDL_JOYSTICK_GAMECUBE_RUMBLE_BRAKE" -/** - * \brief A variable controlling whether Switch Joy-Cons should be treated the same as Switch Pro Controllers when using the HIDAPI driver. - * - * This variable can be set to the following values: - * "0" - basic Joy-Con support with no analog input (the default) - * "1" - Joy-Cons treated as half full Pro Controllers with analog inputs and sensors - * - * This does not combine Joy-Cons into a single controller. That's up to the user. - */ -#define SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS "SDL_JOYSTICK_HIDAPI_JOY_CONS" - /** * \brief A variable controlling whether the HIDAPI driver for Amazon Luna controllers connected via Bluetooth should be used. * @@ -789,6 +778,17 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_SWITCH "SDL_JOYSTICK_HIDAPI_SWITCH" +/** + * \brief A variable controlling whether Nintendo Switch Joy-Con controllers will be combined into a single Pro-like controller when using the HIDAPI driver + * + * This variable can be set to the following values: + * "0" - Left and right Joy-Con controllers will not be combined and each will be a mini-gamepad + * "1" - Left and right Joy-Con controllers will be combined into a single controller (the default) + * + * The default is "1" + */ +#define SDL_HINT_JOYSTICK_HIDAPI_SWITCH_COMBINE_JOY_CONS "SDL_JOYSTICK_HIDAPI_SWITCH_COMBINE_JOY_CONS" + /** * \brief A variable controlling whether the Home button LED should be turned on when a Nintendo Switch controller is opened * diff --git a/src/joystick/SDL_gamecontroller.c b/src/joystick/SDL_gamecontroller.c index 2c0875c9f..483bacd0d 100644 --- a/src/joystick/SDL_gamecontroller.c +++ b/src/joystick/SDL_gamecontroller.c @@ -593,6 +593,10 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI 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 if (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product) || + SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product)) { + /* Mini gamepad mode */ + SDL_strlcat(mapping_string, "a:b0,b:b1,guide:b5,leftshoulder:b9,leftstick:b7,leftx:a0,lefty:a1,rightshoulder:b10,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)); @@ -619,12 +623,6 @@ static ControllerMapping_t *SDL_CreateMappingForHIDAPIController(SDL_JoystickGUI case SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO: /* Nintendo Switch Pro controllers have a screenshot button */ SDL_strlcat(mapping_string, "misc1:b15,", sizeof(mapping_string)); - /* Joy-Cons have extra buttons in the same place as paddles */ - if (SDL_IsJoystickNintendoSwitchJoyConLeft(vendor, product)) { - SDL_strlcat(mapping_string, "paddle2:b17,paddle4:b19,", sizeof(mapping_string)); - } else if (SDL_IsJoystickNintendoSwitchJoyConRight(vendor, product)) { - SDL_strlcat(mapping_string, "paddle1:b16,paddle3:b18,", sizeof(mapping_string)); - } break; case SDL_CONTROLLER_TYPE_AMAZON_LUNA: /* Amazon Luna Controller has a mic button under the guide button */ diff --git a/src/joystick/SDL_joystick.c b/src/joystick/SDL_joystick.c index f9d061f30..c6c4d836c 100644 --- a/src/joystick/SDL_joystick.c +++ b/src/joystick/SDL_joystick.c @@ -1960,8 +1960,10 @@ SDL_GetJoystickGameControllerTypeFromVIDPID(Uint16 vendor, Uint16 product, const } else if (vendor == USB_VENDOR_NVIDIA && product == USB_PRODUCT_NVIDIA_SHIELD_CONTROLLER) { type = SDL_CONTROLLER_TYPE_NVIDIA_SHIELD; - } else if (vendor == USB_VENDOR_NINTENDO && product == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP) { - type = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_FALSE) ? SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO : SDL_CONTROLLER_TYPE_UNKNOWN; + } else if (vendor == USB_VENDOR_NINTENDO && + (product == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP || + product == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_PAIR)) { + type = SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO; } else { switch (GuessControllerType(vendor, product)) { @@ -2000,12 +2002,7 @@ SDL_GetJoystickGameControllerTypeFromVIDPID(Uint16 vendor, Uint16 product, const 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) { - type = SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO; - } else { - type = SDL_GetHintBoolean(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, SDL_FALSE) ? SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO : SDL_CONTROLLER_TYPE_UNKNOWN; - } + type = SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO; break; default: break; diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index a2cb87cb0..02afe2566 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -359,6 +359,12 @@ HIDAPI_DriverSwitch_IsSupportedDevice(const char *name, SDL_GameControllerType t if (SDL_strcmp(name, "HORI Wireless Switch Pad") == 0) { return SDL_FALSE; } + + /* We always support the Nintendo Online NES Controllers */ + if (SDL_strncmp(name, "NES Controller", 14) == 0) { + return SDL_TRUE; + } + return (type == SDL_CONTROLLER_TYPE_NINTENDO_SWITCH_PRO) ? SDL_TRUE : SDL_FALSE; } @@ -1040,17 +1046,21 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti * level and we only care about battery level over bluetooth anyway. */ if (device->vendor_id == USB_VENDOR_NINTENDO && - (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO || - device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP || - device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_LEFT || - device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT)) { + (device->product_id == USB_PRODUCT_NINTENDO_SWITCH_PRO || + device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP || + device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_LEFT || + device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_RIGHT)) { input_mode = k_eSwitchInputReportIDs_FullControllerState; } if (input_mode == k_eSwitchInputReportIDs_FullControllerState) { - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 200.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 200.0f); - ctx->m_bHasSensors = SDL_TRUE; + /* Use the right sensor in the combined Joy-Con pair */ + if (!device->parent || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO, 200.0f); + SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL, 200.0f); + ctx->m_bHasSensors = SDL_TRUE; + } } if (!LoadStickCalibration(ctx, input_mode)) { @@ -1101,6 +1111,9 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti ctx->m_rgucMACAddress[3], ctx->m_rgucMACAddress[4], ctx->m_rgucMACAddress[5]); + if (joystick->serial) { + SDL_free(joystick->serial); + } joystick->serial = SDL_strdup(serial); } } @@ -1118,12 +1131,7 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti } /* Initialize the joystick capabilities */ - if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || - ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - joystick->nbuttons = 20; - } else { - joystick->nbuttons = 16; - } + joystick->nbuttons = 16; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; joystick->epowerlevel = SDL_JOYSTICK_POWER_WIRED; @@ -1216,6 +1224,16 @@ HIDAPI_DriverSwitch_RumbleJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joys return SDL_Unsupported(); } + if (device->parent) { + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + /* Just handle low frequency rumble */ + high_frequency_rumble = 0; + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + /* Just handle high frequency rumble */ + low_frequency_rumble = 0; + } + } + if (ctx->m_bRumblePending) { if (HIDAPI_DriverSwitch_SendPendingRumble(ctx) < 0) { return -1; @@ -1508,33 +1526,14 @@ static void SendSensorUpdate(SDL_Joystick *joystick, SDL_DriverSwitch_Context *c SDL_PrivateJoystickSensor(joystick, type, data, 3); } -static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +static void HandleCombinedControllerStateL(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) { Sint16 axis; - if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { - Uint8 data = packet->controllerState.rgucButtons[0]; - SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_Y), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_PADDLE1, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_PADDLE3, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); - } - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); - axis = (data & 0x80) ? 32767 : -32768; - SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); - } - if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { Uint8 data = packet->controllerState.rgucButtons[1]; SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_MISC1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); } @@ -1544,10 +1543,6 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); - if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_PADDLE4, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); - SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_PADDLE2, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); - } SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); axis = (data & 0x80) ? 32767 : -32768; SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); @@ -1560,6 +1555,59 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); axis = ApplyStickCalibration(ctx, 0, 1, axis); SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis); +} + +static void HandleMiniControllerStateL(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + } + + if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { + Uint8 data = packet->controllerState.rgucButtons[2]; + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_Y), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + } + + axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 0, 0, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis); + + axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); + axis = ApplyStickCalibration(ctx, 0, 1, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, ~axis); +} + +static void HandleCombinedControllerStateR(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_Y), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + axis = (data & 0x80) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + } axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); axis = ApplyStickCalibration(ctx, 1, 0, axis); @@ -1568,6 +1616,104 @@ static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_C axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); axis = ApplyStickCalibration(ctx, 1, 1, axis); SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, ~axis); +} + +static void HandleMiniControllerStateR(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_Y), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + } + + axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 1, 0, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, axis); + + axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); + axis = ApplyStickCalibration(ctx, 1, 1, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); +} + +static void HandleFullControllerState(SDL_Joystick *joystick, SDL_DriverSwitch_Context *ctx, SwitchStatePacket_t *packet) +{ + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + if (ctx->device->parent) { + HandleCombinedControllerStateL(joystick, ctx, packet); + } else { + HandleMiniControllerStateL(joystick, ctx, packet); + } + } else if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + if (ctx->device->parent) { + HandleCombinedControllerStateR(joystick, ctx, packet); + } else { + HandleMiniControllerStateR(joystick, ctx, packet); + } + } else { + Sint16 axis; + + if (packet->controllerState.rgucButtons[0] != ctx->m_lastFullState.controllerState.rgucButtons[0]) { + Uint8 data = packet->controllerState.rgucButtons[0]; + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_A), (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_B), (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_X), (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, RemapButton(ctx, SDL_CONTROLLER_BUTTON_Y), (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + axis = (data & 0x80) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERRIGHT, axis); + } + + if (packet->controllerState.rgucButtons[1] != ctx->m_lastFullState.controllerState.rgucButtons[1]) { + Uint8 data = packet->controllerState.rgucButtons[1]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_BACK, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_START, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_RIGHTSTICK, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSTICK, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_GUIDE, (data & 0x10) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_MISC1, (data & 0x20) ? SDL_PRESSED : SDL_RELEASED); + } + + if (packet->controllerState.rgucButtons[2] != ctx->m_lastFullState.controllerState.rgucButtons[2]) { + Uint8 data = packet->controllerState.rgucButtons[2]; + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_DOWN, (data & 0x01) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_UP, (data & 0x02) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_RIGHT, (data & 0x04) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_DPAD_LEFT, (data & 0x08) ? SDL_PRESSED : SDL_RELEASED); + SDL_PrivateJoystickButton(joystick, SDL_CONTROLLER_BUTTON_LEFTSHOULDER, (data & 0x40) ? SDL_PRESSED : SDL_RELEASED); + axis = (data & 0x80) ? 32767 : -32768; + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_TRIGGERLEFT, axis); + } + + axis = packet->controllerState.rgucJoystickLeft[0] | ((packet->controllerState.rgucJoystickLeft[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 0, 0, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTX, axis); + + axis = ((packet->controllerState.rgucJoystickLeft[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickLeft[2] << 4); + axis = ApplyStickCalibration(ctx, 0, 1, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_LEFTY, ~axis); + + axis = packet->controllerState.rgucJoystickRight[0] | ((packet->controllerState.rgucJoystickRight[1] & 0xF) << 8); + axis = ApplyStickCalibration(ctx, 1, 0, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTX, axis); + + axis = ((packet->controllerState.rgucJoystickRight[1] & 0xF0) >> 4) | (packet->controllerState.rgucJoystickRight[2] << 4); + axis = ApplyStickCalibration(ctx, 1, 1, axis); + SDL_PrivateJoystickAxis(joystick, SDL_CONTROLLER_AXIS_RIGHTY, ~axis); + } /* High nibble of battery/connection byte is battery level, low nibble is connection status * LSB of connection nibble is USB/Switch connection status diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index 2e85580f3..2e66402f6 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -80,6 +80,7 @@ static SDL_SpinLock SDL_HIDAPI_spinlock; static Uint32 SDL_HIDAPI_change_count = 0; static SDL_HIDAPI_Device *SDL_HIDAPI_devices; static int SDL_HIDAPI_numjoysticks = 0; +static SDL_bool SDL_HIDAPI_combine_joycons = SDL_TRUE; static SDL_bool initialized = SDL_FALSE; static SDL_bool shutting_down = SDL_FALSE; @@ -224,6 +225,10 @@ HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device) int i; SDL_GameControllerType type; + if (device->num_children > 0) { + return &SDL_HIDAPI_DriverCombined; + } + if (SDL_ShouldIgnoreJoystick(device->name, device->guid)) { return NULL; } @@ -250,8 +255,12 @@ HIDAPI_GetDeviceDriver(SDL_HIDAPI_Device *device) static SDL_HIDAPI_Device * HIDAPI_GetDeviceByIndex(int device_index, SDL_JoystickID *pJoystickID) { - SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; - while (device) { + SDL_HIDAPI_Device *device; + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->parent) { + continue; + } if (device->driver) { if (device_index < device->num_joysticks) { if (pJoystickID) { @@ -261,7 +270,6 @@ HIDAPI_GetDeviceByIndex(int device_index, SDL_JoystickID *pJoystickID) } device_index -= device->num_joysticks; } - device = device->next; } return NULL; } @@ -269,39 +277,17 @@ HIDAPI_GetDeviceByIndex(int device_index, SDL_JoystickID *pJoystickID) static SDL_HIDAPI_Device * HIDAPI_GetJoystickByInfo(const char *path, Uint16 vendor_id, Uint16 product_id) { - SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; - while (device) { + SDL_HIDAPI_Device *device; + + for (device = SDL_HIDAPI_devices; device; device = device->next) { if (device->vendor_id == vendor_id && device->product_id == product_id && SDL_strcmp(device->path, path) == 0) { break; } - device = device->next; } return device; } -static void -HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device) -{ - if (device->driver) { - return; /* Already setup */ - } - - device->driver = HIDAPI_GetDeviceDriver(device); - if (device->driver) { - 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); - } - } - - /* Initialize the device, which may cause a connected event */ - if (device->driver && !device->driver->InitDevice(device)) { - device->driver = NULL; - } -} - static void HIDAPI_CleanupDeviceDriver(SDL_HIDAPI_Device *device) { @@ -318,6 +304,38 @@ HIDAPI_CleanupDeviceDriver(SDL_HIDAPI_Device *device) device->driver = NULL; } +static void +HIDAPI_SetupDeviceDriver(SDL_HIDAPI_Device *device) +{ + if (device->driver) { + SDL_bool enabled; + + if (device->vendor_id == USB_VENDOR_NINTENDO && device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_PAIR) { + enabled = SDL_HIDAPI_combine_joycons; + } else { + enabled = device->driver->enabled; + } + if (!enabled) { + HIDAPI_CleanupDeviceDriver(device); + } + return; /* Already setup */ + } + + device->driver = HIDAPI_GetDeviceDriver(device); + if (device->driver) { + 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); + } + } + + /* Initialize the device, which may cause a connected event */ + if (device->driver && !device->driver->InitDevice(device)) { + device->driver = NULL; + } +} + static void SDLCALL SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { @@ -330,6 +348,8 @@ SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldVal SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; driver->enabled = SDL_GetHintBoolean(driver->hint, enabled); } + } else if (SDL_strcmp(name, SDL_HINT_JOYSTICK_HIDAPI_SWITCH_COMBINE_JOY_CONS) == 0) { + SDL_HIDAPI_combine_joycons = enabled; } else { for (i = 0; i < SDL_arraysize(SDL_HIDAPI_drivers); ++i) { SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; @@ -351,12 +371,12 @@ SDL_HIDAPIDriverHintChanged(void *userdata, const char *name, const char *oldVal SDL_LockJoysticks(); for (device = SDL_HIDAPI_devices; device; device = device->next) { - if (device->driver && !device->driver->enabled) { - HIDAPI_CleanupDeviceDriver(device); - } HIDAPI_SetupDeviceDriver(device); } + /* Update the device list again to pick up any new devices */ + SDL_HIDAPI_change_count = 0; + SDL_UnlockJoysticks(); } @@ -399,6 +419,8 @@ HIDAPI_JoystickInit(void) SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; SDL_AddHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL); } + SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_COMBINE_JOY_CONS, + SDL_HIDAPIDriverHintChanged, NULL); SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPIDriverHintChanged, NULL); HIDAPI_JoystickDetect(); @@ -409,18 +431,70 @@ HIDAPI_JoystickInit(void) return 0; } -SDL_bool -HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) +static SDL_bool +HIDAPI_AddJoystickInstanceToDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) { - SDL_JoystickID joystickID; SDL_JoystickID *joysticks = (SDL_JoystickID *)SDL_realloc(device->joysticks, (device->num_joysticks + 1)*sizeof(*device->joysticks)); if (!joysticks) { return SDL_FALSE; } - joystickID = SDL_GetNextJoystickInstanceID(); device->joysticks = joysticks; device->joysticks[device->num_joysticks++] = joystickID; + return SDL_TRUE; +} + +static SDL_bool +HIDAPI_DelJoystickInstanceFromDevice(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +{ + int i, size; + + for (i = 0; i < device->num_joysticks; ++i) { + if (device->joysticks[i] == joystickID) { + size = (device->num_joysticks - i - 1) * sizeof(SDL_JoystickID); + SDL_memmove(&device->joysticks[i], &device->joysticks[i+1], size); + --device->num_joysticks; + if (device->num_joysticks == 0) { + SDL_free(device->joysticks); + device->joysticks = NULL; + } + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +static SDL_bool +HIDAPI_JoystickInstanceIsUnique(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) +{ + if (device->parent && device->num_joysticks == 1 && device->parent->num_joysticks == 1 && + device->joysticks[0] == device->parent->joysticks[0]) { + return SDL_FALSE; + } + return SDL_TRUE; +} + +SDL_bool +HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) +{ + int i, j; + SDL_JoystickID joystickID; + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + for (j = child->num_joysticks; j--; ) { + HIDAPI_JoystickDisconnected(child, child->joysticks[j]); + } + } + + joystickID = SDL_GetNextJoystickInstanceID(); + HIDAPI_AddJoystickInstanceToDevice(device, joystickID); + + for (i = 0; i < device->num_children; ++i) { + SDL_HIDAPI_Device *child = device->children[i]; + HIDAPI_AddJoystickInstanceToDevice(child, joystickID); + } + ++SDL_HIDAPI_numjoysticks; SDL_PrivateJoystickAdded(joystickID); @@ -434,29 +508,38 @@ HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID) { - int i, size; + int i, j; + SDL_bool unique = HIDAPI_JoystickInstanceIsUnique(device, joystickID); + + if (!unique) { + /* Disconnecting a child always disconnects the parent */ + device = device->parent; + unique = SDL_TRUE; + } for (i = 0; i < device->num_joysticks; ++i) { if (device->joysticks[i] == joystickID) { - SDL_Joystick *joystick = SDL_JoystickFromInstanceID(joystickID); - if (joystick) { - HIDAPI_JoystickClose(joystick); + if (unique) { + SDL_Joystick *joystick = SDL_JoystickFromInstanceID(joystickID); + if (joystick) { + HIDAPI_JoystickClose(joystick); + } } - size = (device->num_joysticks - i - 1) * sizeof(SDL_JoystickID); - SDL_memmove(&device->joysticks[i], &device->joysticks[i+1], size); - --device->num_joysticks; + HIDAPI_DelJoystickInstanceFromDevice(device, joystickID); - --SDL_HIDAPI_numjoysticks; - if (device->num_joysticks == 0) { - SDL_free(device->joysticks); - device->joysticks = NULL; + for (j = 0; j < device->num_children; ++j) { + SDL_HIDAPI_Device *child = device->children[j]; + HIDAPI_DelJoystickInstanceFromDevice(child, joystickID); } - if (!shutting_down) { - SDL_PrivateJoystickRemoved(joystickID); + if (unique) { + --SDL_HIDAPI_numjoysticks; + + if (!shutting_down) { + SDL_PrivateJoystickRemoved(joystickID); + } } - return; } } } @@ -488,8 +571,8 @@ HIDAPI_ConvertString(const wchar_t *wide_string) return string; } -static void -HIDAPI_AddDevice(struct SDL_hid_device_info *info) +static SDL_HIDAPI_Device * +HIDAPI_AddDevice(const struct SDL_hid_device_info *info, int num_children, SDL_HIDAPI_Device **children) { SDL_HIDAPI_Device *device; SDL_HIDAPI_Device *curr, *last = NULL; @@ -500,12 +583,12 @@ HIDAPI_AddDevice(struct SDL_hid_device_info *info) device = (SDL_HIDAPI_Device *)SDL_calloc(1, sizeof(*device)); if (!device) { - return; + return NULL; } device->path = SDL_strdup(info->path); if (!device->path) { SDL_free(device); - return; + return NULL; } device->seen = SDL_TRUE; device->vendor_id = info->vendor_id; @@ -564,7 +647,17 @@ HIDAPI_AddDevice(struct SDL_hid_device_info *info) SDL_free(device->serial); SDL_free(device->path); SDL_free(device); - return; + return NULL; + } + } + + if (num_children > 0) { + int i; + + device->num_children = num_children; + device->children = children; + for (i = 0; i < num_children; ++i) { + children[i]->parent = device; } } @@ -577,9 +670,12 @@ HIDAPI_AddDevice(struct SDL_hid_device_info *info) HIDAPI_SetupDeviceDriver(device); +#define DEBUG_HIDAPI #ifdef DEBUG_HIDAPI SDL_Log("Added HIDAPI device '%s' VID 0x%.4x, PID 0x%.4x, version %d, serial %s, interface %d, interface_class %d, interface_subclass %d, interface_protocol %d, usage page 0x%.4x, usage 0x%.4x, path = %s, driver = %s (%s)\n", device->name, device->vendor_id, device->product_id, device->version, device->serial ? device->serial : "NONE", device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol, device->usage_page, device->usage, device->path, device->driver ? device->driver->hint : "NONE", device->driver && device->driver->enabled ? "ENABLED" : "DISABLED"); #endif + + return device; } @@ -617,6 +713,62 @@ HIDAPI_DelDevice(SDL_HIDAPI_Device *device) } } +static SDL_bool +HIDAPI_CreateCombinedJoyCons() +{ + SDL_HIDAPI_Device *device, *combined; + SDL_HIDAPI_Device *joycons[2] = { NULL, NULL }; + + if (!SDL_HIDAPI_combine_joycons) { + return SDL_FALSE; + } + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (!device->driver) { + /* Unsupported device */ + continue; + } + if (device->parent) { + /* This device is already part of a combined device */ + continue; + } + + if (!joycons[0] && SDL_IsJoystickNintendoSwitchJoyConLeft(device->vendor_id, device->product_id)) { + joycons[0] = device; + } + if (!joycons[1] && SDL_IsJoystickNintendoSwitchJoyConRight(device->vendor_id, device->product_id)) { + joycons[1] = device; + } + if (joycons[0] && joycons[1]) { + SDL_hid_device_info info; + SDL_HIDAPI_Device **children = (SDL_HIDAPI_Device **)SDL_malloc(2 * sizeof(SDL_HIDAPI_Device *)); + if (!children) { + return SDL_FALSE; + } + children[0] = joycons[0]; + children[1] = joycons[1]; + + SDL_zero(info); + info.path = "nintendo_joycons_combined"; + info.vendor_id = USB_VENDOR_NINTENDO; + info.product_id = USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_PAIR; + info.interface_number = -1; + info.usage_page = USB_USAGEPAGE_GENERIC_DESKTOP; + info.usage = USB_USAGE_GENERIC_GAMEPAD; + info.manufacturer_string = L"Nintendo"; + info.product_string = L"Switch Joy-Con (L/R)"; + + combined = HIDAPI_AddDevice(&info, 2, children); + if (combined && combined->driver) { + return SDL_TRUE; + } else { + return SDL_FALSE; + } + } + } + return SDL_FALSE; +} + static void HIDAPI_UpdateDeviceList(void) { @@ -626,10 +778,11 @@ HIDAPI_UpdateDeviceList(void) SDL_LockJoysticks(); /* Prepare the existing device list */ - device = SDL_HIDAPI_devices; - while (device) { + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->children) { + continue; + } device->seen = SDL_FALSE; - device = device->next; } /* Enumerate the devices */ @@ -641,7 +794,7 @@ HIDAPI_UpdateDeviceList(void) if (device) { device->seen = SDL_TRUE; } else { - HIDAPI_AddDevice(info); + HIDAPI_AddDevice(info, 0, NULL); } } SDL_hid_free_enumeration(devs); @@ -649,17 +802,39 @@ HIDAPI_UpdateDeviceList(void) } /* Remove any devices that weren't seen or have been disconnected due to read errors */ +check_removed: device = SDL_HIDAPI_devices; while (device) { SDL_HIDAPI_Device *next = device->next; if (!device->seen || (device->driver && device->num_joysticks == 0 && !device->dev)) { - HIDAPI_DelDevice(device); + if (device->parent) { + /* When a child device goes away, so does the parent */ + int i; + device = device->parent; + for (i = 0; i < device->num_children; ++i) { + HIDAPI_DelDevice(device->children[i]); + } + HIDAPI_DelDevice(device); + + /* Update the device list again to pick up any children left */ + SDL_HIDAPI_change_count = 0; + + /* We deleted more than one device here, restart the loop */ + goto check_removed; + } else { + HIDAPI_DelDevice(device); + } } device = next; } + /* See if we can create any combined Joy-Con controllers */ + while (HIDAPI_CreateCombinedJoyCons()) { + continue; + } + SDL_UnlockJoysticks(); } @@ -710,14 +885,12 @@ HIDAPI_IsDeviceTypePresent(SDL_GameControllerType type) } SDL_LockJoysticks(); - device = SDL_HIDAPI_devices; - while (device) { + for (device = SDL_HIDAPI_devices; device; device = device->next) { if (device->driver && SDL_GetJoystickGameControllerProtocol(device->name, device->vendor_id, device->product_id, device->interface_number, device->interface_class, device->interface_subclass, device->interface_protocol) == type) { result = SDL_TRUE; break; } - device = device->next; } SDL_UnlockJoysticks(); @@ -764,14 +937,12 @@ HIDAPI_IsDevicePresent(Uint16 vendor_id, Uint16 product_id, Uint16 version, cons and we have something similar in our device list, mark it as present. */ SDL_LockJoysticks(); - device = SDL_HIDAPI_devices; - while (device) { + for (device = SDL_HIDAPI_devices; device; device = device->next) { if (device->driver && HIDAPI_IsEquivalentToDevice(vendor_id, product_id, device)) { result = SDL_TRUE; break; } - device = device->next; } SDL_UnlockJoysticks(); @@ -787,8 +958,8 @@ HIDAPI_JoystickDetect(void) if (SDL_AtomicTryLock(&SDL_HIDAPI_spinlock)) { Uint32 count = SDL_hid_device_change_count(); if (SDL_HIDAPI_change_count != count) { - HIDAPI_UpdateDeviceList(); SDL_HIDAPI_change_count = count; + HIDAPI_UpdateDeviceList(); } SDL_AtomicUnlock(&SDL_HIDAPI_spinlock); } @@ -803,8 +974,10 @@ HIDAPI_UpdateDevices(void) /* Prepare the existing device list */ if (SDL_AtomicTryLock(&SDL_HIDAPI_spinlock)) { - device = SDL_HIDAPI_devices; - while (device) { + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->parent) { + continue; + } if (device->driver) { if (SDL_TryLockMutex(device->dev_lock) == 0) { device->updating = SDL_TRUE; @@ -813,7 +986,6 @@ HIDAPI_UpdateDevices(void) SDL_UnlockMutex(device->dev_lock); } } - device = device->next; } SDL_AtomicUnlock(&SDL_HIDAPI_spinlock); } @@ -1065,7 +1237,18 @@ HIDAPI_JoystickQuit(void) SDL_HIDAPI_QuitRumble(); while (SDL_HIDAPI_devices) { - HIDAPI_DelDevice(SDL_HIDAPI_devices); + SDL_HIDAPI_Device *device = SDL_HIDAPI_devices; + if (device->parent) { + /* When a child device goes away, so does the parent */ + int i; + device = device->parent; + for (i = 0; i < device->num_children; ++i) { + HIDAPI_DelDevice(device->children[i]); + } + HIDAPI_DelDevice(device); + } else { + HIDAPI_DelDevice(device); + } } /* Make sure the drivers cleaned up properly */ @@ -1075,6 +1258,8 @@ HIDAPI_JoystickQuit(void) SDL_HIDAPI_DeviceDriver *driver = SDL_HIDAPI_drivers[i]; SDL_DelHintCallback(driver->hint, SDL_HIDAPIDriverHintChanged, NULL); } + SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI_SWITCH_COMBINE_JOY_CONS, + SDL_HIDAPIDriverHintChanged, NULL); SDL_DelHintCallback(SDL_HINT_JOYSTICK_HIDAPI, SDL_HIDAPIDriverHintChanged, NULL); diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index 6718200d3..3f10bf384 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -82,6 +82,10 @@ typedef struct _SDL_HIDAPI_Device /* Used to flag that the device is being updated */ SDL_bool updating; + struct _SDL_HIDAPI_Device *parent; + int num_children; + struct _SDL_HIDAPI_Device **children; + struct _SDL_HIDAPI_Device *next; } SDL_HIDAPI_Device; @@ -110,6 +114,7 @@ typedef struct _SDL_HIDAPI_DeviceDriver /* HIDAPI device support */ +extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverCombined; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverGameCube; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverLuna; extern SDL_HIDAPI_DeviceDriver SDL_HIDAPI_DriverShield; diff --git a/src/joystick/usb_ids.h b/src/joystick/usb_ids.h index ef7aa26bf..d12b90651 100644 --- a/src/joystick/usb_ids.h +++ b/src/joystick/usb_ids.h @@ -48,6 +48,7 @@ #define USB_PRODUCT_NINTENDO_SWITCH_PRO 0x2009 #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_PAIR 0x2008 /* Used by joycond */ #define USB_PRODUCT_NINTENDO_SWITCH_JOY_CON_GRIP 0x200e #define USB_PRODUCT_NINTENDO_N64_CONTROLLER 0x2019 #define USB_PRODUCT_NINTENDO_SEGA_GENESIS_CONTROLLER 0x201e @@ -61,7 +62,7 @@ #define USB_PRODUCT_SONY_DS4_SLIM 0x09cc #define USB_PRODUCT_SONY_DS5 0x0ce6 #define USB_PRODUCT_VICTRIX_FS_PRO_V2 0x0207 -#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 /* XUSB driver software PID */ +#define USB_PRODUCT_XBOX360_XUSB_CONTROLLER 0x02a1 /* XUSB driver software PID */ #define USB_PRODUCT_XBOX360_WIRED_CONTROLLER 0x028e #define USB_PRODUCT_XBOX360_WIRELESS_RECEIVER 0x0719 #define USB_PRODUCT_XBOX_ONE_ADAPTIVE 0x0b0a @@ -82,8 +83,8 @@ #define USB_PRODUCT_XBOX_SERIES_X_PDP_AFTERGLOW 0x02da #define USB_PRODUCT_XBOX_SERIES_X_POWERA_FUSION_PRO2 0x4001 #define USB_PRODUCT_XBOX_SERIES_X_POWERA_SPECTRA 0x4002 -#define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff /* XBOXGIP driver software PID */ -#define USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER 0x02fe /* Made up product ID for XInput */ +#define USB_PRODUCT_XBOX_ONE_XBOXGIP_CONTROLLER 0x02ff /* XBOXGIP driver software PID */ +#define USB_PRODUCT_XBOX_ONE_XINPUT_CONTROLLER 0x02fe /* Made up product ID for XInput */ #define USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD 0x11ff /* USB usage pages */ diff --git a/test/testgamecontroller.c b/test/testgamecontroller.c index ef54d698d..fce349a14 100644 --- a/test/testgamecontroller.c +++ b/test/testgamecontroller.c @@ -764,7 +764,6 @@ main(int argc, char *argv[]) char guid[64]; SDL_SetHint(SDL_HINT_ACCELEROMETER_AS_JOYSTICK, "0"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_JOY_CONS, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1");