Added support for the Rotor Riot gamepad, and upcoming Xbox and PS4 controller support on iOS and tvOS
authorSam Lantinga
Thu, 06 Jun 2019 08:20:53 -0700
changeset 12763e539b6245d43
parent 12762 0fa532733f3f
child 12764 5bd8243f0a35
Added support for the Rotor Riot gamepad, and upcoming Xbox and PS4 controller support on iOS and tvOS
Patch contributed by Nat Brown
src/joystick/SDL_gamecontrollerdb.h
src/joystick/iphoneos/SDL_sysjoystick.m
     1.1 --- a/src/joystick/SDL_gamecontrollerdb.h	Wed Jun 05 12:03:45 2019 -0700
     1.2 +++ b/src/joystick/SDL_gamecontrollerdb.h	Thu Jun 06 08:20:53 2019 -0700
     1.3 @@ -589,9 +589,13 @@
     1.4  #if defined(SDL_JOYSTICK_MFI)
     1.5      "05000000ac0500000100000000006d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,",
     1.6      "05000000ac0500000200000000006d02,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,rightshoulder:b5,x:b2,y:b3,",
     1.7 +    "05000000ac0500000400000000006d04,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,",
     1.8 +    "05000000ac0500000500000000006d05,*,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
     1.9 +    "030000004c050000cc09000000000000,DUALSHOCK 4 Wireless Controller,a:b1,b:b2,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b9,x:b0,y:b3,",
    1.10      "05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,",
    1.11      "05000000de2800000511000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
    1.12      "05000000de2800000611000001000000,Steam Controller,a:b0,b:b1,back:b6,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,righttrigger:a3,start:b7,x:b2,y:b3,",
    1.13 +    "030000005e040000e002000000000000,Xbox Wireless Controller,a:b0,b:b1,back:b10,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b6,leftshoulder:b4,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
    1.14  #endif
    1.15  #if defined(SDL_JOYSTICK_EMSCRIPTEN)
    1.16      "default,Standard Gamepad,a:b0,b:b1,back:b8,dpdown:b13,dpleft:b14,dpright:b15,dpup:b12,guide:b16,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,",
     2.1 --- a/src/joystick/iphoneos/SDL_sysjoystick.m	Wed Jun 05 12:03:45 2019 -0700
     2.2 +++ b/src/joystick/iphoneos/SDL_sysjoystick.m	Thu Jun 06 08:20:53 2019 -0700
     2.3 @@ -48,6 +48,35 @@
     2.4  
     2.5  static id connectObserver = nil;
     2.6  static id disconnectObserver = nil;
     2.7 +
     2.8 +#include <Availability.h>
     2.9 +#include <objc/message.h>
    2.10 +
    2.11 +// remove compilation warnings for strict builds by defining these selectors, even though
    2.12 +// they are only ever used indirectly through objc_msgSend
    2.13 +@interface GCExtendedGamepad (SDL)
    2.14 +#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
    2.15 +@property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
    2.16 +@property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions;
    2.17 +#endif
    2.18 +#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 121000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1401000)
    2.19 +@property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
    2.20 +@property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
    2.21 +#endif
    2.22 +@end
    2.23 +
    2.24 +#define BUTTON_INDEX_A  0
    2.25 +#define BUTTON_INDEX_B  1
    2.26 +#define BUTTON_INDEX_X  2
    2.27 +#define BUTTON_INDEX_Y  3
    2.28 +#define BUTTON_INDEX_LEFT_SHOULDER  4
    2.29 +#define BUTTON_INDEX_RIGHT_SHOULDER  5
    2.30 +#define BUTTON_INDEX_GUIDE  6
    2.31 +#define BUTTON_INDEX_LEFT_THUMBSTICK  7
    2.32 +#define BUTTON_INDEX_RIGHT_THUMBSTICK  8
    2.33 +#define BUTTON_INDEX_START  9
    2.34 +#define BUTTON_INDEX_BACK  10
    2.35 +
    2.36  #endif /* SDL_JOYSTICK_MFI */
    2.37  
    2.38  #if !TARGET_OS_TV
    2.39 @@ -82,6 +111,8 @@
    2.40  {
    2.41  #ifdef SDL_JOYSTICK_MFI
    2.42      const Uint16 VENDOR_APPLE = 0x05AC;
    2.43 +    const Uint16 VENDOR_MICROSOFT = 0x045e;
    2.44 +    const Uint16 VENDOR_SONY = 0x054C;
    2.45      Uint16 *guid16 = (Uint16 *)device->guid.data;
    2.46      Uint16 vendor = 0;
    2.47      Uint16 product = 0;
    2.48 @@ -104,12 +135,45 @@
    2.49      device->name = SDL_strdup(name);
    2.50  
    2.51      if (controller.extendedGamepad) {
    2.52 -        vendor = VENDOR_APPLE;
    2.53 -        product = 1;
    2.54 -        subtype = 1;
    2.55 +        int nbuttons = 7; /* ABXY, shoulder buttons, pause button */
    2.56 +
    2.57 +        if ([controller.extendedGamepad respondsToSelector:@selector(buttonMenu)]
    2.58 +            && ((id (*)(id, SEL))objc_msgSend)(controller.extendedGamepad, @selector(buttonMenu))) {
    2.59 +            // if we see .buttonMenu, then .buttonOption, .leftThumbstickButton (L3) & .rightThumbstickButton (R3)
    2.60 +            // also exist (ios13+, macOS10.15+), though some may be nil, hold a spot for them
    2.61 +            nbuttons = 11;
    2.62 +        } else if ([controller.extendedGamepad respondsToSelector:@selector(leftThumbstickButton)]
    2.63 +                   && ((id (*)(id, SEL))objc_msgSend)(controller.extendedGamepad, @selector(leftThumbstickButton))) {
    2.64 +            // if we didn't see .buttonMenu but do see .leftThumbstickButton (L3), then .rightThumbstickButton (R3) 
    2.65 +            // also exists (ios12.1+, macos10.14.1+). unlikely for R3 to be nil if L3 is not, but update code
    2.66 +            // will never report a button change for R3 even so
    2.67 +            nbuttons = 9;
    2.68 +        }
    2.69 +
    2.70 +        if ([controller.vendorName containsString: @"Xbox"]) {
    2.71 +            vendor = VENDOR_MICROSOFT;
    2.72 +            product = 0x02E0; // assume Xbox One S BLE Controller unless/until GCController flows VID/PID
    2.73 +        } else if ([controller.vendorName containsString: @"DUALSHOCK"]) {
    2.74 +            vendor = VENDOR_SONY;
    2.75 +            product = 0x09CC; // assume DS4 Slim unless/until GCController flows VID/PID
    2.76 +        } else if (nbuttons == 9) {
    2.77 +            // unknown MFi controller with L3/R3 buttons (e.g. Rotor Riot)
    2.78 +            vendor = VENDOR_APPLE;
    2.79 +            product = 4;
    2.80 +            subtype = 4;
    2.81 +        } else if (nbuttons == 11) {
    2.82 +            // unkonwn MFi controller with L3/R3 and menu/options buttons (no known instances, future proofing)
    2.83 +            vendor = VENDOR_APPLE;
    2.84 +            product = 5;
    2.85 +            subtype = 5;
    2.86 +        } else {
    2.87 +            vendor = VENDOR_APPLE;
    2.88 +            product = 1;
    2.89 +            subtype = 1;
    2.90 +        }
    2.91          device->naxes = 6; /* 2 thumbsticks and 2 triggers */
    2.92          device->nhats = 1; /* d-pad */
    2.93 -        device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
    2.94 +        device->nbuttons = nbuttons;
    2.95      } else if (controller.gamepad) {
    2.96          vendor = VENDOR_APPLE;
    2.97          product = 2;
    2.98 @@ -525,13 +589,25 @@
    2.99              };
   2.100  
   2.101              /* Button order matches the XInput Windows mappings. */
   2.102 -            Uint8 buttons[] = {
   2.103 -                gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
   2.104 -                gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
   2.105 -                gamepad.leftShoulder.isPressed,
   2.106 -                gamepad.rightShoulder.isPressed,
   2.107 -                joystick->delayed_guide_button,
   2.108 -            };
   2.109 +            Uint8 buttons[joystick->nbuttons];
   2.110 +            buttons[BUTTON_INDEX_A] = gamepad.buttonA.isPressed;
   2.111 +            buttons[BUTTON_INDEX_B] = gamepad.buttonB.isPressed;
   2.112 +            buttons[BUTTON_INDEX_X] = gamepad.buttonX.isPressed;
   2.113 +            buttons[BUTTON_INDEX_Y] = gamepad.buttonY.isPressed;
   2.114 +            buttons[BUTTON_INDEX_LEFT_SHOULDER] = gamepad.leftShoulder.isPressed;
   2.115 +            buttons[BUTTON_INDEX_RIGHT_SHOULDER] = gamepad.rightShoulder.isPressed;
   2.116 +            buttons[BUTTON_INDEX_GUIDE] = joystick->delayed_guide_button;
   2.117 +
   2.118 +            // previously checked for availability of these iOS12.1+/macOS10.14.1+ or iOS13+/macOS10.15+
   2.119 +            // selectors. they exist but may be nil, in which case objc_msgSend will return 0/false for isPressed
   2.120 +            if (joystick->nbuttons > 8) {
   2.121 +                buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(leftThumbstickButton)), @selector(isPressed) );
   2.122 +                buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(rightThumbstickButton)), @selector(isPressed) );
   2.123 +            }
   2.124 +            if (joystick->nbuttons > 10) {
   2.125 +                buttons[BUTTON_INDEX_START] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(buttonMenu)), @selector(isPressed) );
   2.126 +                buttons[BUTTON_INDEX_BACK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(buttonOptions)), @selector(isPressed) );
   2.127 +            }
   2.128  
   2.129              hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
   2.130  
   2.131 @@ -601,9 +677,8 @@
   2.132          }
   2.133  
   2.134          for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   2.135 -            const Uint8 pausebutton = joystick->nbuttons - 1; /* The pause button is always last. */
   2.136 -            SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED);
   2.137 -            SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED);
   2.138 +            SDL_PrivateJoystickButton(joystick, BUTTON_INDEX_GUIDE, SDL_PRESSED);
   2.139 +            SDL_PrivateJoystickButton(joystick, BUTTON_INDEX_GUIDE, SDL_RELEASED);
   2.140              updateplayerindex = YES;
   2.141          }
   2.142          joystick->hwdata->num_pause_presses = 0;