From aa403beac9a0a49e413da3189f59ed0b4c080cf7 Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 2 Jan 2025 14:27:38 -0800 Subject: [PATCH] Replaced SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE and SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE with SDL_HINT_JOYSTICK_ENHANCED_REPORTS Also added support for SDL_HINT_JOYSTICK_ENHANCED_REPORTS for Nintendo Switch controllers --- include/SDL3/SDL_hints.h | 70 +++------ src/joystick/hidapi/SDL_hidapi_ps4.c | 74 +++++----- src/joystick/hidapi/SDL_hidapi_ps5.c | 84 +++++------ src/joystick/hidapi/SDL_hidapi_switch.c | 189 ++++++++++++++++++------ test/testcontroller.c | 9 +- 5 files changed, 240 insertions(+), 186 deletions(-) diff --git a/include/SDL3/SDL_hints.h b/include/SDL3/SDL_hints.h index c380d26fd2933..67cb08a6648af 100644 --- a/include/SDL3/SDL_hints.h +++ b/include/SDL3/SDL_hints.h @@ -1264,6 +1264,25 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_DEVICE "SDL_JOYSTICK_DEVICE" +/** + * A variable controlling whether enhanced reports should be used for controllers when using the HIDAPI driver. + * + * Enhanced reports allow rumble and effects on PlayStation controllers and gyro on Nintendo Switch controllers, but break Windows DirectInput for other applications that don't use SDL. + * + * Once enhanced reports are enabled, they can't be disabled on PlayStation controllers without power cycling the controller. + * + * The variable can be set to the following values: + * + * - "0": enhanced reports are not enabled. + * - "1": enhanced reports are enabled. (default) + * - "auto": enhanced features are advertised to the application, but SDL doesn't change the controller report mode unless the application explicitly requests it. + * + * This hint can be enabled anytime. + * + * \since This hint is available since SDL 3.1.3. + */ +#define SDL_HINT_JOYSTICK_ENHANCED_REPORTS "SDL_JOYSTICK_ENHANCED_REPORTS" + /** * A variable containing a list of flightstick style controllers. * @@ -1571,32 +1590,6 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL "SDL_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL" -/** - * A variable controlling whether extended input reports should be used for - * PS4 controllers when using the HIDAPI driver. - * - * The variable can be set to the following values: - * - * - "0": extended reports are not enabled. (default) - * - "1": extended reports are enabled. - * - * Extended input reports allow rumble on Bluetooth PS4 controllers, but break - * DirectInput handling for applications that don't use SDL. - * - * Once extended reports are enabled, they can not be disabled without power - * cycling the controller. - * - * For compatibility with applications written for versions of SDL prior to - * the introduction of PS5 controller support, this value will also control - * the state of extended reports on PS5 controllers when the - * SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE hint is not explicitly set. - * - * This hint can be enabled anytime. - * - * \since This hint is available since SDL 3.1.3. - */ -#define SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE "SDL_JOYSTICK_HIDAPI_PS4_RUMBLE" - /** * A variable controlling whether the HIDAPI driver for PS5 controllers should * be used. @@ -1627,31 +1620,6 @@ extern "C" { */ #define SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED "SDL_JOYSTICK_HIDAPI_PS5_PLAYER_LED" -/** - * A variable controlling whether extended input reports should be used for - * PS5 controllers when using the HIDAPI driver. - * - * The variable can be set to the following values: - * - * - "0": extended reports are not enabled. (default) - * - "1": extended reports. - * - * Extended input reports allow rumble on Bluetooth PS5 controllers, but break - * DirectInput handling for applications that don't use SDL. - * - * Once extended reports are enabled, they can not be disabled without power - * cycling the controller. - * - * For compatibility with applications written for versions of SDL prior to - * the introduction of PS5 controller support, this value defaults to the - * value of SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE. - * - * This hint can be enabled anytime. - * - * \since This hint is available since SDL 3.1.3. - */ -#define SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE "SDL_JOYSTICK_HIDAPI_PS5_RUMBLE" - /** * A variable controlling whether the HIDAPI driver for NVIDIA SHIELD * controllers should be used. diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index b9da38092fc31..20973af99b433 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -129,18 +129,16 @@ typedef struct } IMUCalibrationData; /* Rumble hint mode: - * default: enhanced features are available if the controller is using enhanced reports * "0": enhanced features are never used * "1": enhanced features are always used * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. */ typedef enum { - PS4_RUMBLE_HINT_DEFAULT, - PS4_RUMBLE_HINT_OFF, - PS4_RUMBLE_HINT_ON, - PS4_RUMBLE_HINT_AUTO -} SDL_PS4_RumbleHintMode; + PS4_ENHANCED_REPORT_HINT_OFF, + PS4_ENHANCED_REPORT_HINT_ON, + PS4_ENHANCED_REPORT_HINT_AUTO +} HIDAPI_PS4_EnhancedReportHint; typedef struct { @@ -154,7 +152,7 @@ typedef struct bool vibration_supported; bool touchpad_supported; bool effects_supported; - SDL_PS4_RumbleHintMode rumble_hint; + HIDAPI_PS4_EnhancedReportHint enhanced_report_hint; bool enhanced_reports; bool enhanced_mode; bool enhanced_mode_available; @@ -696,7 +694,7 @@ static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) SDL_HIDAPI_SendRumbleAndUnlock(device, data, sizeof(data)); } } else { -#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controllers, except it +#if 0 /* The 8BitDo Zero 2 has perfect emulation of a PS4 controller, except it * only sends reports when the state changes, so we can't disconnect here. */ // We can't even send an invalid effects packet, or it will put the controller in enhanced mode @@ -709,6 +707,9 @@ static void HIDAPI_DriverPS4_TickleBluetooth(SDL_HIDAPI_Device *device) static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx) { + if (ctx->enhanced_mode_available) { + return; + } ctx->enhanced_mode_available = true; if (ctx->touchpad_supported) { @@ -730,9 +731,7 @@ static void HIDAPI_DriverPS4_SetEnhancedModeAvailable(SDL_DriverPS4_Context *ctx static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx) { - if (!ctx->enhanced_mode_available) { - HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); - } + HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); if (!ctx->enhanced_mode) { ctx->enhanced_mode = true; @@ -742,55 +741,52 @@ static void HIDAPI_DriverPS4_SetEnhancedMode(SDL_DriverPS4_Context *ctx) } } -static void HIDAPI_DriverPS4_SetRumbleHintMode(SDL_DriverPS4_Context *ctx, SDL_PS4_RumbleHintMode rumble_hint) +static void HIDAPI_DriverPS4_SetEnhancedReportHint(SDL_DriverPS4_Context *ctx, HIDAPI_PS4_EnhancedReportHint enhanced_report_hint) { - switch (rumble_hint) { - case PS4_RUMBLE_HINT_DEFAULT: - if (ctx->enhanced_reports) { - HIDAPI_DriverPS4_SetEnhancedMode(ctx); - } - break; - case PS4_RUMBLE_HINT_OFF: + switch (enhanced_report_hint) { + case PS4_ENHANCED_REPORT_HINT_OFF: // Nothing to do, enhanced mode is a one-way ticket break; - case PS4_RUMBLE_HINT_ON: + case PS4_ENHANCED_REPORT_HINT_ON: HIDAPI_DriverPS4_SetEnhancedMode(ctx); break; - case PS4_RUMBLE_HINT_AUTO: + case PS4_ENHANCED_REPORT_HINT_AUTO: HIDAPI_DriverPS4_SetEnhancedModeAvailable(ctx); break; } - ctx->rumble_hint = rumble_hint; + ctx->enhanced_report_hint = enhanced_report_hint; } static void HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS4_Context *ctx) { ctx->enhanced_reports = true; - if (ctx->rumble_hint == PS4_RUMBLE_HINT_DEFAULT) { - HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON); + if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); } } static void HIDAPI_DriverPS4_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS4_Context *ctx) { - if (ctx->rumble_hint == PS4_RUMBLE_HINT_AUTO) { - HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON); + if (ctx->enhanced_report_hint == PS4_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); } } -static void SDLCALL SDL_PS4RumbleHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +static void SDLCALL SDL_PS4EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { SDL_DriverPS4_Context *ctx = (SDL_DriverPS4_Context *)userdata; - if (!hint) { - HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_DEFAULT); - } else if (SDL_strcasecmp(hint, "auto") == 0) { - HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_AUTO); - } else if (SDL_GetStringBoolean(hint, false)) { - HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_ON); + if (ctx->device->is_bluetooth) { + if (hint && SDL_strcasecmp(hint, "auto") == 0) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_AUTO); + } else if (SDL_GetStringBoolean(hint, true)) { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); + } else { + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_OFF); + } } else { - HIDAPI_DriverPS4_SetRumbleHintMode(ctx, PS4_RUMBLE_HINT_OFF); + HIDAPI_DriverPS4_SetEnhancedReportHint(ctx, PS4_ENHANCED_REPORT_HINT_ON); } } @@ -868,8 +864,8 @@ static bool HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL, SDL_PS4ReportIntervalHintChanged, ctx); - SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, - SDL_PS4RumbleHintChanged, ctx); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS4EnhancedReportsChanged, ctx); return true; } @@ -1287,7 +1283,7 @@ static bool HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) case k_EPS4ReportIdBluetoothState7: case k_EPS4ReportIdBluetoothState8: case k_EPS4ReportIdBluetoothState9: - // This is the extended report, we can enable effects now in default mode + // This is the extended report, we can enable effects now in auto mode HIDAPI_DriverPS4_UpdateEnhancedModeOnEnhancedReport(ctx); // Bluetooth state packets have two additional bytes at the beginning, the first notes if HID is present @@ -1353,8 +1349,8 @@ static void HIDAPI_DriverPS4_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_REPORT_INTERVAL, SDL_PS4ReportIntervalHintChanged, ctx); - SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, - SDL_PS4RumbleHintChanged, ctx); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS4EnhancedReportsChanged, ctx); ctx->joystick = NULL; } diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index b462eabb8aecf..b124cfea1fa9a 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -215,18 +215,16 @@ typedef struct } IMUCalibrationData; /* Rumble hint mode: - * default: enhanced features are available if the controller is using enhanced reports * "0": enhanced features are never used * "1": enhanced features are always used * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. */ typedef enum { - PS5_RUMBLE_HINT_DEFAULT, - PS5_RUMBLE_HINT_OFF, - PS5_RUMBLE_HINT_ON, - PS5_RUMBLE_HINT_AUTO -} SDL_PS5_RumbleHintMode; + PS5_ENHANCED_REPORT_HINT_OFF, + PS5_ENHANCED_REPORT_HINT_ON, + PS5_ENHANCED_REPORT_HINT_AUTO +} HIDAPI_PS5_EnhancedReportHint; typedef struct { @@ -240,7 +238,7 @@ typedef struct bool playerled_supported; bool touchpad_supported; bool effects_supported; - SDL_PS5_RumbleHintMode rumble_hint; + HIDAPI_PS5_EnhancedReportHint enhanced_report_hint; bool enhanced_reports; bool enhanced_mode; bool enhanced_mode_available; @@ -803,6 +801,9 @@ static void HIDAPI_DriverPS5_TickleBluetooth(SDL_HIDAPI_Device *device) static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx) { + if (ctx->enhanced_mode_available) { + return; + } ctx->enhanced_mode_available = true; if (ctx->touchpad_supported) { @@ -828,11 +829,9 @@ static void HIDAPI_DriverPS5_SetEnhancedModeAvailable(SDL_DriverPS5_Context *ctx static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx) { - if (!ctx->enhanced_mode_available) { - HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); - } + HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); - if (!ctx->enhanced_mode && ctx->enhanced_mode_available) { + if (!ctx->enhanced_mode) { ctx->enhanced_mode = true; // Switch into enhanced report mode @@ -843,63 +842,52 @@ static void HIDAPI_DriverPS5_SetEnhancedMode(SDL_DriverPS5_Context *ctx) } } -static void HIDAPI_DriverPS5_SetRumbleHintMode(SDL_DriverPS5_Context *ctx, SDL_PS5_RumbleHintMode rumble_hint) +static void HIDAPI_DriverPS5_SetEnhancedReportHint(SDL_DriverPS5_Context *ctx, HIDAPI_PS5_EnhancedReportHint enhanced_report_hint) { - switch (rumble_hint) { - case PS5_RUMBLE_HINT_DEFAULT: - if (ctx->enhanced_reports) { - HIDAPI_DriverPS5_SetEnhancedMode(ctx); - } - break; - case PS5_RUMBLE_HINT_OFF: + switch (enhanced_report_hint) { + case PS5_ENHANCED_REPORT_HINT_OFF: // Nothing to do, enhanced mode is a one-way ticket break; - case PS5_RUMBLE_HINT_ON: + case PS5_ENHANCED_REPORT_HINT_ON: HIDAPI_DriverPS5_SetEnhancedMode(ctx); break; - case PS5_RUMBLE_HINT_AUTO: + case PS5_ENHANCED_REPORT_HINT_AUTO: HIDAPI_DriverPS5_SetEnhancedModeAvailable(ctx); break; } - ctx->rumble_hint = rumble_hint; + ctx->enhanced_report_hint = enhanced_report_hint; } static void HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(SDL_DriverPS5_Context *ctx) { ctx->enhanced_reports = true; - if (ctx->rumble_hint == PS5_RUMBLE_HINT_DEFAULT) { - HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON); + if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); } } static void HIDAPI_DriverPS5_UpdateEnhancedModeOnApplicationUsage(SDL_DriverPS5_Context *ctx) { - if (ctx->rumble_hint == PS5_RUMBLE_HINT_AUTO) { - HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON); + if (ctx->enhanced_report_hint == PS5_ENHANCED_REPORT_HINT_AUTO) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); } } -static void SDLCALL SDL_PS5RumbleHintChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +static void SDLCALL SDL_PS5EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) { SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)userdata; - if (!hint) { - /* Games written prior the introduction of PS5 controller support in SDL will not be aware of - SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, but they did know SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE. - To support apps that only knew about the PS4 hint, we'll use the PS4 hint as the default. - */ - hint = SDL_GetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE); - } - - if (!hint) { - HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_DEFAULT); - } else if (SDL_strcasecmp(hint, "auto") == 0) { - HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_AUTO); - } else if (SDL_GetStringBoolean(hint, false)) { - HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_ON); + if (ctx->device->is_bluetooth) { + if (hint && SDL_strcasecmp(hint, "auto") == 0) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_AUTO); + } else if (SDL_GetStringBoolean(hint, true)) { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); + } else { + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_OFF); + } } else { - HIDAPI_DriverPS5_SetRumbleHintMode(ctx, PS5_RUMBLE_HINT_OFF); + HIDAPI_DriverPS5_SetEnhancedReportHint(ctx, PS5_ENHANCED_REPORT_HINT_ON); } } @@ -962,9 +950,9 @@ static bool HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystic joystick->nhats = 1; joystick->firmware_version = ctx->firmware_version; - SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, - SDL_PS5RumbleHintChanged, ctx); - SDL_AddHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS5EnhancedReportsChanged, ctx); + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, SDL_PS5PlayerLEDHintChanged, ctx); return true; @@ -1529,7 +1517,7 @@ static bool HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) } break; case k_EPS5ReportIdBluetoothState: - // This is the extended report, we can enable effects now in default mode + // This is the extended report, we can enable effects now in auto mode HIDAPI_DriverPS5_UpdateEnhancedModeOnEnhancedReport(ctx); if (ctx->use_alternate_report) { @@ -1592,8 +1580,8 @@ static void HIDAPI_DriverPS5_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joysti { SDL_DriverPS5_Context *ctx = (SDL_DriverPS5_Context *)device->context; - SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, - SDL_PS5RumbleHintChanged, ctx); + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_PS5EnhancedReportsChanged, ctx); SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_PS5_PLAYER_LED, SDL_PS5PlayerLEDHintChanged, ctx); diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index 2010e7b4649fd..674b54f98d795 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -268,6 +268,18 @@ typedef struct } SwitchProprietaryOutputPacket_t; #pragma pack() +/* Enhanced report hint mode: + * "0": enhanced features are never used + * "1": enhanced features are always used + * "auto": enhanced features are advertised to the application, but SDL doesn't touch the controller state unless the application explicitly requests it. + */ +typedef enum +{ + SWITCH_ENHANCED_REPORT_HINT_OFF, + SWITCH_ENHANCED_REPORT_HINT_ON, + SWITCH_ENHANCED_REPORT_HINT_AUTO +} HIDAPI_Switch_EnhancedReportHint; + typedef struct { SDL_HIDAPI_Device *device; @@ -283,6 +295,9 @@ typedef struct Uint8 m_nCurrentInputMode; Uint8 m_rgucMACAddress[6]; Uint8 m_nCommandNumber; + HIDAPI_Switch_EnhancedReportHint m_eEnhancedReportHint; + bool m_bEnhancedMode; + bool m_bEnhancedModeAvailable; SwitchCommonOutputPacket_t m_RumblePacket; Uint8 m_rgucReadBuffer[k_unSwitchMaxOutputPacketLength]; bool m_bRumbleActive; @@ -290,6 +305,7 @@ typedef struct bool m_bRumblePending; bool m_bRumbleZeroPending; Uint32 m_unRumblePending; + bool m_bSensorsSupported; bool m_bReportSensors; bool m_bHasSensorData; Uint64 m_ulLastInput; @@ -777,24 +793,25 @@ static Uint8 GetDefaultInputMode(SDL_DriverSwitch_Context *ctx) } } - /* The official Nintendo Switch Pro Controller supports FullControllerState over Bluetooth - * just fine. We really should use that, or else the epowerlevel code in HandleFullControllerState - * is completely pointless. We need full state if we want battery level and we only care about - * battery level over Bluetooth anyway. - */ - if (ctx->device->vendor_id == USB_VENDOR_NINTENDO) { - // However, switching to full controller state breaks DirectInput, so let's not do that - #if 0 - input_mode = k_eSwitchInputReportIDs_FullControllerState; - #endif - - /* However, Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode, + switch (ctx->m_eEnhancedReportHint) { + case SWITCH_ENHANCED_REPORT_HINT_OFF: + input_mode = k_eSwitchInputReportIDs_SimpleControllerState; + break; + case SWITCH_ENHANCED_REPORT_HINT_ON: + if (input_mode == k_eSwitchInputReportIDs_SimpleControllerState) { + input_mode = k_eSwitchInputReportIDs_FullControllerState; + } + break; + case SWITCH_ENHANCED_REPORT_HINT_AUTO: + /* Joy-Con controllers switch their thumbsticks into D-pad mode in simple mode, * so let's enable full controller state for them. */ - if (ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || + if (ctx->device->vendor_id == USB_VENDOR_NINTENDO && + ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT || ctx->device->product_id == USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT) { input_mode = k_eSwitchInputReportIDs_FullControllerState; } + break; } return input_mode; } @@ -813,6 +830,103 @@ static Uint8 GetSensorInputMode(SDL_DriverSwitch_Context *ctx) return input_mode; } +static void UpdateInputMode(SDL_DriverSwitch_Context *ctx) +{ + Uint8 input_mode; + + if (ctx->m_bReportSensors) { + input_mode = GetSensorInputMode(ctx); + } else { + input_mode = GetDefaultInputMode(ctx); + } + SetInputMode(ctx, input_mode); +} + +static void SetEnhancedModeAvailable(SDL_DriverSwitch_Context *ctx) +{ + if (ctx->m_bEnhancedModeAvailable) { + return; + } + ctx->m_bEnhancedModeAvailable = true; + + if (ctx->m_bSensorsSupported) { + // Use the right sensor in the combined Joy-Con pair + if (!ctx->device->parent || + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO, 200.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL, 200.0f); + } + if (ctx->device->parent && + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_L, 200.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_L, 200.0f); + } + if (ctx->device->parent && + ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_GYRO_R, 200.0f); + SDL_PrivateJoystickAddSensor(ctx->joystick, SDL_SENSOR_ACCEL_R, 200.0f); + } + } +} + +static void SetEnhancedMode(SDL_DriverSwitch_Context *ctx, bool bEnabled) +{ + if (bEnabled) { + SetEnhancedModeAvailable(ctx); + } + + if (bEnabled != ctx->m_bEnhancedMode) { + ctx->m_bEnhancedMode = bEnabled; + + UpdateInputMode(ctx); + } +} + +static void SetEnhancedReportHint(SDL_DriverSwitch_Context *ctx, HIDAPI_Switch_EnhancedReportHint eEnhancedReportHint) +{ + switch (eEnhancedReportHint) { + case SWITCH_ENHANCED_REPORT_HINT_OFF: + SetEnhancedMode(ctx, false); + break; + case SWITCH_ENHANCED_REPORT_HINT_ON: + SetEnhancedMode(ctx, true); + break; + case SWITCH_ENHANCED_REPORT_HINT_AUTO: + SetEnhancedModeAvailable(ctx); + break; + } + ctx->m_eEnhancedReportHint = eEnhancedReportHint; + + UpdateInputMode(ctx); +} + +static void UpdateEnhancedModeOnEnhancedReport(SDL_DriverSwitch_Context *ctx) +{ + if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); + } +} + +static void UpdateEnhancedModeOnApplicationUsage(SDL_DriverSwitch_Context *ctx) +{ + if (ctx->m_eEnhancedReportHint == SWITCH_ENHANCED_REPORT_HINT_AUTO) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); + } +} + +static void SDLCALL SDL_EnhancedReportsChanged(void *userdata, const char *name, const char *oldValue, const char *hint) +{ + SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)userdata; + + if (hint && SDL_strcasecmp(hint, "auto") == 0) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_AUTO); + } else if (SDL_GetStringBoolean(hint, true)) { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_ON); + } else { + SetEnhancedReportHint(ctx, SWITCH_ENHANCED_REPORT_HINT_OFF); + } +} + static bool SetIMUEnabled(SDL_DriverSwitch_Context *ctx, bool enabled) { Uint8 imu_data = enabled ? 1 : 0; @@ -1432,35 +1546,16 @@ static bool HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joys ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_N64 && ctx->m_eControllerType != k_eSwitchDeviceInfoControllerType_SEGA_Genesis) { if (LoadIMUCalibration(ctx)) { - // 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); - } - if (device->parent && - ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft) { - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_L, 200.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_L, 200.0f); - } - if (device->parent && - ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_GYRO_R, 200.0f); - SDL_PrivateJoystickAddSensor(joystick, SDL_SENSOR_ACCEL_R, 200.0f); - } + ctx->m_bSensorsSupported = true; } } - if (!SetVibrationEnabled(ctx, 1)) { - SDL_SetError("Couldn't enable vibration"); - return false; - } + // Enable vibration + SetVibrationEnabled(ctx, 1); // Set desired input mode - if (!SetInputMode(ctx, GetDefaultInputMode(ctx))) { - SDL_SetError("Couldn't set input mode"); - return false; - } + SDL_AddHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_EnhancedReportsChanged, ctx); // Start sending USB reports if (!device->is_bluetooth) { @@ -1694,20 +1789,20 @@ static bool HIDAPI_DriverSwitch_SendJoystickEffect(SDL_HIDAPI_Device *device, SD static bool HIDAPI_DriverSwitch_SetJoystickSensorsEnabled(SDL_HIDAPI_Device *device, SDL_Joystick *joystick, bool enabled) { SDL_DriverSwitch_Context *ctx = (SDL_DriverSwitch_Context *)device->context; - Uint8 input_mode; - if (enabled) { - input_mode = GetSensorInputMode(ctx); - } else { - input_mode = GetDefaultInputMode(ctx); + UpdateEnhancedModeOnApplicationUsage(ctx); + + if (!ctx->m_bSensorsSupported || (enabled && !ctx->m_bEnhancedMode)) { + return SDL_Unsupported(); } - SetInputMode(ctx, input_mode); - SetIMUEnabled(ctx, enabled); ctx->m_bReportSensors = enabled; ctx->m_unIMUSamples = 0; ctx->m_ulIMUSampleTimestampNS = SDL_GetTicksNS(); + UpdateInputMode(ctx); + SetIMUEnabled(ctx, enabled); + return true; } @@ -2585,6 +2680,9 @@ static bool HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) break; case k_eSwitchInputReportIDs_FullControllerState: case k_eSwitchInputReportIDs_FullControllerAndMcuState: + // This is the extended report, we can enable sensors now in auto mode + UpdateEnhancedModeOnEnhancedReport(ctx); + HandleFullControllerState(joystick, ctx, (SwitchStatePacket_t *)&ctx->m_rgucReadBuffer[1]); break; default: @@ -2652,6 +2750,9 @@ static void HIDAPI_DriverSwitch_CloseJoystick(SDL_HIDAPI_Device *device, SDL_Joy } } + SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, + SDL_EnhancedReportsChanged, ctx); + if (ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConLeft || ctx->m_eControllerType == k_eSwitchDeviceInfoControllerType_JoyConRight) { SDL_RemoveHintCallback(SDL_HINT_JOYSTICK_HIDAPI_JOYCON_HOME_LED, diff --git a/test/testcontroller.c b/test/testcontroller.c index 6b5874156b35d..2d09072fca78c 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -910,8 +910,10 @@ static void AddController(SDL_JoystickID id, bool verbose) new_controller->id = id; new_controller->joystick = SDL_OpenJoystick(id); - new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); - new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); + if (new_controller->joystick) { + new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); + new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); + } joystick = new_controller->joystick; if (joystick) { @@ -2002,8 +2004,7 @@ int main(int argc, char *argv[]) } SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS4_RUMBLE, "1"); - SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_PS5_RUMBLE, "1"); + SDL_SetHint(SDL_HINT_JOYSTICK_ENHANCED_REPORTS, "auto"); SDL_SetHint(SDL_HINT_JOYSTICK_HIDAPI_STEAM, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ROG_CHAKRAM, "1"); SDL_SetHint(SDL_HINT_JOYSTICK_ALLOW_BACKGROUND_EVENTS, "1");