Added support for the Rotor Riot gamepad, and upcoming Xbox and PS4 controller support on iOS and tvOS
Patch contributed by Nat Brown
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;