diff --git a/src/joystick/hidapi/SDL_hidapi_ps4.c b/src/joystick/hidapi/SDL_hidapi_ps4.c index d1c422cd0..6925be4ae 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps4.c +++ b/src/joystick/hidapi/SDL_hidapi_ps4.c @@ -124,7 +124,6 @@ typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; SDL_bool is_dongle; - SDL_bool is_bluetooth; SDL_bool official_controller; SDL_bool sensors_supported; SDL_bool lightbar_supported; @@ -268,7 +267,7 @@ HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) /* Check for type of connection */ ctx->is_dongle = (device->vendor_id == USB_VENDOR_SONY && device->product_id == USB_PRODUCT_SONY_DS4_DONGLE); if (ctx->is_dongle) { - ctx->is_bluetooth = SDL_FALSE; + device->is_bluetooth = SDL_FALSE; ctx->official_controller = SDL_TRUE; ctx->enhanced_mode = SDL_TRUE; } else if (device->vendor_id == USB_VENDOR_SONY) { @@ -277,10 +276,10 @@ HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) if (size >= 7) { SDL_snprintf(serial, sizeof(serial), "%.2x-%.2x-%.2x-%.2x-%.2x-%.2x", data[6], data[5], data[4], data[3], data[2], data[1]); - ctx->is_bluetooth = SDL_FALSE; + device->is_bluetooth = SDL_FALSE; ctx->enhanced_mode = SDL_TRUE; } else { - ctx->is_bluetooth = SDL_TRUE; + device->is_bluetooth = SDL_TRUE; /* Read a report to see if we're in enhanced mode */ size = SDL_hid_read_timeout(device->dev, data, sizeof(data), 16); @@ -300,11 +299,11 @@ HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) ctx->official_controller = SDL_TRUE; } else { /* Third party controllers appear to all be wired */ - ctx->is_bluetooth = SDL_FALSE; + device->is_bluetooth = SDL_FALSE; ctx->enhanced_mode = SDL_TRUE; } #ifdef DEBUG_PS4 - SDL_Log("PS4 dongle = %s, bluetooth = %s\n", ctx->is_dongle ? "TRUE" : "FALSE", ctx->is_bluetooth ? "TRUE" : "FALSE"); + SDL_Log("PS4 dongle = %s, bluetooth = %s\n", ctx->is_dongle ? "TRUE" : "FALSE", device->is_bluetooth ? "TRUE" : "FALSE"); #endif /* Get the device capabilities */ @@ -374,6 +373,14 @@ HIDAPI_DriverPS4_InitDevice(SDL_HIDAPI_Device *device) } HIDAPI_SetDeviceSerial(device, serial); + /* Prefer the USB device over the Bluetooth device */ + if (device->is_bluetooth) { + if (HIDAPI_HasConnectedUSBDevice(device->serial)) { + return SDL_TRUE; + } + } else { + HIDAPI_DisconnectBluetoothDevice(device->serial); + } return HIDAPI_JoystickConnected(device, NULL); } @@ -408,7 +415,7 @@ HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device) return; } - if (ctx->is_bluetooth) { + if (device->is_bluetooth) { size = ReadFeatureReport(device->dev, k_ePS4FeatureReportIdGyroCalibration_BT, data, sizeof(data)); if (size < 35) { #ifdef DEBUG_PS4_CALIBRATION @@ -454,7 +461,7 @@ HIDAPI_DriverPS4_LoadCalibrationData(SDL_HIDAPI_Device *device) sGyroYawBias = LOAD16(data[3], data[4]); sGyroRollBias = LOAD16(data[5], data[6]); - if (ctx->is_bluetooth || ctx->is_dongle) { + if (device->is_bluetooth || ctx->is_dongle) { sGyroPitchPlus = LOAD16(data[7], data[8]); sGyroYawPlus = LOAD16(data[9], data[10]); sGyroRollPlus = LOAD16(data[11], data[12]); @@ -657,7 +664,7 @@ HIDAPI_DriverPS4_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) /* Initialize the joystick capabilities */ joystick->nbuttons = ctx->touchpad_supported ? 16 : 15; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; - joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; + joystick->epowerlevel = device->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; if (ctx->enhanced_mode) { /* Force initialization when opening the joystick */ @@ -743,7 +750,7 @@ HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joy SDL_zeroa(data); - if (ctx->is_bluetooth) { + if (device->is_bluetooth) { data[0] = k_EPS4ReportIdBluetoothEffects; data[1] = 0xC0 | 0x04; /* Magic value HID + CRC, also sets interval to 4ms for samples */ data[3] = 0x03; /* 0x1 is rumble, 0x2 is lightbar, 0x4 is the blink interval */ @@ -760,7 +767,7 @@ HIDAPI_DriverPS4_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joy SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); - if (ctx->is_bluetooth) { + if (device->is_bluetooth) { /* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */ Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */ Uint32 unCRC; @@ -950,6 +957,13 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) int size; int packet_count = 0; + /* Reconnect the Bluetooth device once the USB device is gone */ + if (device->is_bluetooth && + device->num_joysticks == 0 && + !HIDAPI_HasConnectedUSBDevice(device->serial)) { + HIDAPI_JoystickConnected(device, NULL); + } + if (device->num_joysticks > 0) { joystick = SDL_JoystickFromInstanceID(device->joysticks[0]); } else { @@ -997,7 +1011,7 @@ HIDAPI_DriverPS4_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (ctx->is_bluetooth && packet_count == 0) { + if (device->is_bluetooth && packet_count == 0) { /* Check to see if it looks like the device disconnected */ if (SDL_TICKS_PASSED(SDL_GetTicks(), ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { /* Send an empty output report to tickle the Bluetooth stack */ diff --git a/src/joystick/hidapi/SDL_hidapi_ps5.c b/src/joystick/hidapi/SDL_hidapi_ps5.c index 5f72d56c0..390a9c20e 100644 --- a/src/joystick/hidapi/SDL_hidapi_ps5.c +++ b/src/joystick/hidapi/SDL_hidapi_ps5.c @@ -198,7 +198,6 @@ typedef struct { typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; - SDL_bool is_bluetooth; SDL_bool use_alternate_report; SDL_bool sensors_supported; SDL_bool lightbar_supported; @@ -377,15 +376,15 @@ HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) #endif if (size == 64) { /* Connected over USB */ - ctx->is_bluetooth = SDL_FALSE; + device->is_bluetooth = SDL_FALSE; ctx->enhanced_mode = SDL_TRUE; } else if (size > 0 && data[0] == k_EPS5ReportIdBluetoothEffects) { /* Connected over Bluetooth, using enhanced reports */ - ctx->is_bluetooth = SDL_TRUE; + device->is_bluetooth = SDL_TRUE; ctx->enhanced_mode = SDL_TRUE; } else { /* Connected over Bluetooth, using simple reports (DirectInput enabled) */ - ctx->is_bluetooth = SDL_TRUE; + device->is_bluetooth = SDL_TRUE; /* 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. @@ -479,6 +478,14 @@ HIDAPI_DriverPS5_InitDevice(SDL_HIDAPI_Device *device) } HIDAPI_SetDeviceSerial(device, serial); + /* Prefer the USB device over the Bluetooth device */ + if (device->is_bluetooth) { + if (HIDAPI_HasConnectedUSBDevice(device->serial)) { + return SDL_TRUE; + } + } else { + HIDAPI_DisconnectBluetoothDevice(device->serial); + } return HIDAPI_JoystickConnected(device, NULL); } @@ -618,7 +625,7 @@ HIDAPI_DriverPS5_UpdateEffects(SDL_HIDAPI_Device *device, int effect_mask) SDL_zero(effects); /* Make sure the Bluetooth connection sequence has completed before sending LED color change */ - if (ctx->is_bluetooth && + if (device->is_bluetooth && (effect_mask & (k_EDS5EffectLED | k_EDS5EffectPadLights)) != 0) { if (ctx->led_reset_state != k_EDS5LEDResetStateComplete) { ctx->led_reset_state = k_EDS5LEDResetStatePending; @@ -819,7 +826,7 @@ HIDAPI_DriverPS5_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joystick) /* Initialize the joystick capabilities */ joystick->nbuttons = ctx->touchpad_supported ? 17 : 15; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; - joystick->epowerlevel = ctx->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; + joystick->epowerlevel = device->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; joystick->firmware_version = ctx->firmware_version; if (ctx->enhanced_mode) { @@ -916,7 +923,7 @@ HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joy SDL_zeroa(data); - if (ctx->is_bluetooth) { + if (device->is_bluetooth) { data[0] = k_EPS5ReportIdBluetoothEffects; data[1] = 0x02; /* Magic value */ @@ -931,7 +938,7 @@ HIDAPI_DriverPS5_SendJoystickEffect(SDL_HIDAPI_Device *device, SDL_Joystick *joy SDL_memcpy(&data[offset], effect, SDL_min((sizeof(data) - offset), (size_t)size)); - if (ctx->is_bluetooth) { + if (device->is_bluetooth) { /* Bluetooth reports need a CRC at the end of the packet (at least on Linux) */ Uint8 ubHdr = 0xA2; /* hidp header is part of the CRC calculation */ Uint32 unCRC; @@ -1206,7 +1213,7 @@ HIDAPI_DriverPS5_HandleStatePacket(SDL_Joystick *joystick, SDL_hid_device *dev, /* A check of packet->ucBatteryLevel & 0x10 should work as a check for BT vs USB but doesn't * seem to always work. Possibly related to being 100% charged? */ - if (!ctx->is_bluetooth) { + if (!ctx->device->is_bluetooth) { /* 0x20 set means fully charged */ SDL_PrivateJoystickBatteryLevel(joystick, SDL_JOYSTICK_POWER_WIRED); } else { @@ -1258,6 +1265,13 @@ HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) int size; int packet_count = 0; + /* Reconnect the Bluetooth device once the USB device is gone */ + if (device->is_bluetooth && + device->num_joysticks == 0 && + !HIDAPI_HasConnectedUSBDevice(device->serial)) { + HIDAPI_JoystickConnected(device, NULL); + } + if (device->num_joysticks > 0) { joystick = SDL_JoystickFromInstanceID(device->joysticks[0]); } else { @@ -1311,7 +1325,7 @@ HIDAPI_DriverPS5_UpdateDevice(SDL_HIDAPI_Device *device) } } - if (ctx->is_bluetooth && packet_count == 0) { + if (device->is_bluetooth && packet_count == 0) { /* Check to see if it looks like the device disconnected */ if (SDL_TICKS_PASSED(SDL_GetTicks(), ctx->last_packet + BLUETOOTH_DISCONNECT_TIMEOUT_MS)) { /* Send an empty output report to tickle the Bluetooth stack */ diff --git a/src/joystick/hidapi/SDL_hidapi_switch.c b/src/joystick/hidapi/SDL_hidapi_switch.c index d2e840400..b2cbcd1b1 100644 --- a/src/joystick/hidapi/SDL_hidapi_switch.c +++ b/src/joystick/hidapi/SDL_hidapi_switch.c @@ -238,7 +238,6 @@ typedef struct { SDL_HIDAPI_Device *device; SDL_Joystick *joystick; SDL_bool m_bInputOnly; - SDL_bool m_bUsingBluetooth; SDL_bool m_bIsGameCube; SDL_bool m_bUseButtonLabels; SDL_bool m_bPlayerLights; @@ -383,7 +382,7 @@ static void ConstructSubcommand(SDL_DriverSwitch_Context *ctx, ESwitchSubcommand static SDL_bool WritePacket(SDL_DriverSwitch_Context *ctx, void *pBuf, Uint8 ucLen) { Uint8 rgucBuf[k_unSwitchMaxOutputPacketLength]; - const size_t unWriteSize = ctx->m_bUsingBluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; + const size_t unWriteSize = ctx->device->is_bluetooth ? k_unSwitchBluetoothPacketLength : k_unSwitchUSBPacketLength; if (ucLen > k_unSwitchOutputPacketDataLength) { return SDL_FALSE; @@ -584,7 +583,7 @@ static SDL_bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx) { SwitchSubcommandInputPacket_t *reply = NULL; - ctx->m_bUsingBluetooth = SDL_FALSE; + ctx->device->is_bluetooth = SDL_FALSE; if (WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_Status, NULL, 0, SDL_TRUE)) { SwitchProprietaryStatusPacket_t *status = (SwitchProprietaryStatusPacket_t *)&ctx->m_rgucReadBuffer[0]; @@ -598,7 +597,7 @@ static SDL_bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx) return SDL_TRUE; } - ctx->m_bUsingBluetooth = SDL_TRUE; + ctx->device->is_bluetooth = SDL_TRUE; if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) { // Byte 2: Controller ID (1=LJC, 2=RJC, 3=Pro) @@ -610,7 +609,7 @@ static SDL_bool BReadDeviceInfo(SDL_DriverSwitch_Context *ctx) return SDL_TRUE; } - ctx->m_bUsingBluetooth = SDL_FALSE; + ctx->device->is_bluetooth = SDL_FALSE; return SDL_FALSE; } @@ -945,7 +944,7 @@ ReadJoyConControllerType(SDL_HIDAPI_Device *device) } else { SwitchSubcommandInputPacket_t *reply = NULL; - ctx->m_bUsingBluetooth = SDL_TRUE; + device->is_bluetooth = SDL_TRUE; if (WriteSubcommand(ctx, k_eSwitchSubcommandIDs_RequestDeviceInfo, NULL, 0, &reply)) { eControllerType = CalculateControllerType(ctx, (ESwitchDeviceInfoControllerType)reply->deviceInfo.ucDeviceType); } @@ -1244,6 +1243,14 @@ HIDAPI_DriverSwitch_InitDevice(SDL_HIDAPI_Device *device) UpdateDeviceIdentity(device); } + /* Prefer the USB device over the Bluetooth device */ + if (device->is_bluetooth) { + if (HIDAPI_HasConnectedUSBDevice(device->serial)) { + return SDL_TRUE; + } + } else { + HIDAPI_DisconnectBluetoothDevice(device->serial); + } return HIDAPI_JoystickConnected(device, NULL); } @@ -1282,7 +1289,7 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[0]); SetNeutralRumble(&ctx->m_RumblePacket.rumbleData[1]); - if (!ctx->m_bUsingBluetooth) { + if (!device->is_bluetooth) { if (!BTrySetupUSB(ctx)) { SDL_SetError("Couldn't setup USB mode"); return SDL_FALSE; @@ -1290,7 +1297,7 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti } /* Determine the desired input mode (needed before loading stick calibration) */ - if (ctx->m_bUsingBluetooth) { + if (device->is_bluetooth) { input_mode = k_eSwitchInputReportIDs_SimpleControllerState; } else { input_mode = k_eSwitchInputReportIDs_FullControllerState; @@ -1351,7 +1358,7 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti } /* Start sending USB reports */ - if (!ctx->m_bUsingBluetooth) { + if (!device->is_bluetooth) { /* ForceUSB doesn't generate an ACK, so don't wait for a reply */ if (!WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE)) { SDL_SetError("Couldn't start USB reports"); @@ -1390,7 +1397,7 @@ HIDAPI_DriverSwitch_OpenJoystick(SDL_HIDAPI_Device *device, SDL_Joystick *joysti /* Initialize the joystick capabilities */ joystick->nbuttons = 16; joystick->naxes = SDL_CONTROLLER_AXIS_MAX; - joystick->epowerlevel = ctx->m_bUsingBluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; + joystick->epowerlevel = device->is_bluetooth ? SDL_JOYSTICK_POWER_UNKNOWN : SDL_JOYSTICK_POWER_WIRED; /* Set up for input */ ctx->m_bSyncWrite = SDL_FALSE; @@ -2069,6 +2076,13 @@ HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) int size; Uint32 now; + /* Reconnect the Bluetooth device once the USB device is gone */ + if (device->is_bluetooth && + device->num_joysticks == 0 && + !HIDAPI_HasConnectedUSBDevice(device->serial)) { + HIDAPI_JoystickConnected(device, NULL); + } + if (device->num_joysticks > 0) { joystick = SDL_JoystickFromInstanceID(device->joysticks[0]); } else { @@ -2101,14 +2115,14 @@ HIDAPI_DriverSwitch_UpdateDevice(SDL_HIDAPI_Device *device) } if (joystick) { - if (!ctx->m_bInputOnly && !ctx->m_bUsingBluetooth && + if (!ctx->m_bInputOnly && !device->is_bluetooth && ctx->device->product_id != USB_PRODUCT_NINTENDO_SWITCH_JOYCON_GRIP) { const Uint32 INPUT_WAIT_TIMEOUT_MS = 100; if (SDL_TICKS_PASSED(now, ctx->m_unLastInput + INPUT_WAIT_TIMEOUT_MS)) { /* Steam may have put the controller back into non-reporting mode */ WriteProprietary(ctx, k_eSwitchProprietaryCommandIDs_ForceUSB, NULL, 0, SDL_FALSE); } - } else if (ctx->m_bUsingBluetooth) { + } else if (device->is_bluetooth) { const Uint32 INPUT_WAIT_TIMEOUT_MS = 3000; if (SDL_TICKS_PASSED(now, ctx->m_unLastInput + INPUT_WAIT_TIMEOUT_MS)) { /* Bluetooth may have disconnected, try reopening the controller */ diff --git a/src/joystick/hidapi/SDL_hidapijoystick.c b/src/joystick/hidapi/SDL_hidapijoystick.c index f5d4674bb..4900b38ea 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick.c +++ b/src/joystick/hidapi/SDL_hidapijoystick.c @@ -529,6 +529,41 @@ HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial) } } +SDL_bool +HIDAPI_HasConnectedUSBDevice(const char *serial) +{ + SDL_HIDAPI_Device *device; + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (device->is_bluetooth) { + continue; + } + + if (device->serial && SDL_strcmp(serial, device->serial) == 0) { + return SDL_TRUE; + } + } + return SDL_FALSE; +} + +void +HIDAPI_DisconnectBluetoothDevice(const char *serial) +{ + SDL_HIDAPI_Device *device; + + for (device = SDL_HIDAPI_devices; device; device = device->next) { + if (!device->is_bluetooth) { + continue; + } + + if (device->serial && SDL_strcmp(serial, device->serial) == 0) { + while (device->num_joysticks && device->joysticks) { + HIDAPI_JoystickDisconnected(device, device->joysticks[0]); + } + } + } +} + SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID) { diff --git a/src/joystick/hidapi/SDL_hidapijoystick_c.h b/src/joystick/hidapi/SDL_hidapijoystick_c.h index f1c7b2b35..a03221c1e 100644 --- a/src/joystick/hidapi/SDL_hidapijoystick_c.h +++ b/src/joystick/hidapi/SDL_hidapijoystick_c.h @@ -73,6 +73,7 @@ typedef struct _SDL_HIDAPI_Device int interface_protocol; Uint16 usage_page; /* Available on Windows and Mac OS X */ Uint16 usage; /* Available on Windows and Mac OS X */ + SDL_bool is_bluetooth; SDL_JoystickType joystick_type; SDL_GameControllerType type; @@ -157,6 +158,8 @@ extern void HIDAPI_UpdateDevices(void); extern void HIDAPI_SetDeviceName(SDL_HIDAPI_Device *device, const char *name); extern void HIDAPI_SetDeviceProduct(SDL_HIDAPI_Device *device, Uint16 product_id); extern void HIDAPI_SetDeviceSerial(SDL_HIDAPI_Device *device, const char *serial); +extern SDL_bool HIDAPI_HasConnectedUSBDevice(const char *serial); +extern void HIDAPI_DisconnectBluetoothDevice(const char *serial); extern SDL_bool HIDAPI_JoystickConnected(SDL_HIDAPI_Device *device, SDL_JoystickID *pJoystickID); extern void HIDAPI_JoystickDisconnected(SDL_HIDAPI_Device *device, SDL_JoystickID joystickID);