Fixed bug 4996 - Mac: XBoxOne Bluetooth rumble isn't working
authorSam Lantinga <slouken@libsdl.org>
Sun, 01 Mar 2020 14:52:49 -0800
changeset 1356375b324ae7bf6
parent 13562 e1a1a5a3e551
child 13564 1017052e86b7
Fixed bug 4996 - Mac: XBoxOne Bluetooth rumble isn't working

rofferom

I have an annoying issue on MacOS about XBoxOne Bluetooth rumble (Vendor: 0x045e, Product: 0x02fd).

When 360controller is installed, rumble is working correctly. However, Bluetooth rumble isn't working at all, with or without 360controller installed (although it is working with Chrome + https://html5gamepad.com).

I looked at the code, and it seems that XBox controllers are managed in MacOS in this file: SDL_hidapi_xbox360.c. The XBoxOne file is disabled for MacOS in SDL_hidjoystick_c.h.

The function HIDAPI_DriverXbox360_Rumble() is called correctly, and hid_write() returns no error.

I have tried a stupid test. I took the rumble packet from 360controller: https://github.com/360Controller/360Controller/blob/ec4e88eb2d2535e9b32561c702f42fb22b0a7f99/XBOBTFF/FFDriver.cpp#L620. With the patch I have attached, I manage to have rumble working on Bluetooth (with some stupid vibration level, but it proves it can if the packet is changed).

But it breaks the USB rumble with 360controller. A comment in the function makes an explicit reference to 360controller, I think that's why I have broken this specific usecase.

I don't know what is the correct way to fix this, but it seems that the current implementation has a missing case for Bluetooth support.


Note that I also tested master this morning, and I have another issue:
if (!device->ffservice) {
return SDL_Unsupported();
}

test fails in DARWIN_JoystickRumble(). This test has been done quickly, I'm not totaly confident about its accuracy.
src/joystick/hidapi/SDL_hidapi_xbox360.c
     1.1 --- a/src/joystick/hidapi/SDL_hidapi_xbox360.c	Sun Mar 01 13:01:53 2020 -0800
     1.2 +++ b/src/joystick/hidapi/SDL_hidapi_xbox360.c	Sun Mar 01 14:52:49 2020 -0800
     1.3 @@ -245,6 +245,20 @@
     1.4  #endif /* SDL_JOYSTICK_HIDAPI_WINDOWS_GAMING_INPUT */
     1.5  
     1.6  static SDL_bool
     1.7 +IsBluetoothXboxOneController(Uint16 vendor_id, Uint16 product_id)
     1.8 +{
     1.9 +    /* Check to see if it's the Xbox One S or Xbox One Elite Series 2 in Bluetooth mode */
    1.10 +    if (vendor_id == USB_VENDOR_MICROSOFT) {
    1.11 +        if (product_id == USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH ||
    1.12 +            product_id == USB_PRODUCT_XBOX_ONE_S_REV2_BLUETOOTH ||
    1.13 +            product_id == USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH) {
    1.14 +            return SDL_TRUE;
    1.15 +        }
    1.16 +    }
    1.17 +    return SDL_FALSE;
    1.18 +}
    1.19 +
    1.20 +static SDL_bool
    1.21  HIDAPI_DriverXbox360_IsSupportedDevice(const char *name, SDL_GameControllerType type, Uint16 vendor_id, Uint16 product_id, Uint16 version, int interface_number, int interface_class, int interface_subclass, int interface_protocol)
    1.22  {
    1.23      const int XB360W_IFACE_PROTOCOL = 129; /* Wireless */
    1.24 @@ -403,23 +417,38 @@
    1.25  #else /* !__WIN32__ */
    1.26  
    1.27  #ifdef __MACOSX__
    1.28 -    /* On Mac OS X the 360Controller driver uses this short report,
    1.29 -       and we need to prefix it with a magic token so hidapi passes it through untouched
    1.30 -     */
    1.31 -    Uint8 rumble_packet[] = { 'M', 'A', 'G', 'I', 'C', '0', 0x00, 0x04, 0x00, 0x00 };
    1.32 +    if (IsBluetoothXboxOneController(device->vendor_id, device->product_id)) {
    1.33 +        Uint8 rumble_packet[] = { 0x03, 0x0F, 0x00, 0x00, 0x00, 0x00, 0xff, 0x00, 0x00 };
    1.34 +
    1.35 +        rumble_packet[4] = (low_frequency_rumble >> 8);
    1.36 +        rumble_packet[5] = (high_frequency_rumble >> 8);
    1.37  
    1.38 -    rumble_packet[6+2] = (low_frequency_rumble >> 8);
    1.39 -    rumble_packet[6+3] = (high_frequency_rumble >> 8);
    1.40 +        if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
    1.41 +            return SDL_SetError("Couldn't send rumble packet");
    1.42 +        }
    1.43 +    } else {
    1.44 +        /* On Mac OS X the 360Controller driver uses this short report,
    1.45 +           and we need to prefix it with a magic token so hidapi passes it through untouched
    1.46 +         */
    1.47 +        Uint8 rumble_packet[] = { 'M', 'A', 'G', 'I', 'C', '0', 0x00, 0x04, 0x00, 0x00 };
    1.48 +
    1.49 +        rumble_packet[6+2] = (low_frequency_rumble >> 8);
    1.50 +        rumble_packet[6+3] = (high_frequency_rumble >> 8);
    1.51 +
    1.52 +        if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
    1.53 +            return SDL_SetError("Couldn't send rumble packet");
    1.54 +        }
    1.55 +    }
    1.56  #else
    1.57      Uint8 rumble_packet[] = { 0x00, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
    1.58  
    1.59      rumble_packet[3] = (low_frequency_rumble >> 8);
    1.60      rumble_packet[4] = (high_frequency_rumble >> 8);
    1.61 -#endif
    1.62  
    1.63      if (SDL_HIDAPI_SendRumble(device, rumble_packet, sizeof(rumble_packet)) != sizeof(rumble_packet)) {
    1.64          return SDL_SetError("Couldn't send rumble packet");
    1.65      }
    1.66 +#endif
    1.67  #endif /* __WIN32__ */
    1.68  
    1.69      return 0;