Added support for the Rotor Riot gamepad, and upcoming Xbox and PS4 controller support on iOS and tvOS Patch contributed by Nat Brown
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h
index c5bade9..0a6c091 100644
--- a/src/joystick/SDL_gamecontrollerdb.h
+++ b/src/joystick/SDL_gamecontrollerdb.h
@@ -589,9 +589,13 @@ static const char *s_ControllerMappings [] =
#if defined(SDL_JOYSTICK_MFI)
"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,",
"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,",
+ "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,",
+ "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,",
+ "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,",
"05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,",
"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,",
"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,",
+ "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,",
#endif
#if defined(SDL_JOYSTICK_EMSCRIPTEN)
"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,",
diff --git a/src/joystick/iphoneos/SDL_sysjoystick.m b/src/joystick/iphoneos/SDL_sysjoystick.m
index 4bb9395..d4b145a 100644
--- a/src/joystick/iphoneos/SDL_sysjoystick.m
+++ b/src/joystick/iphoneos/SDL_sysjoystick.m
@@ -48,6 +48,35 @@
static id connectObserver = nil;
static id disconnectObserver = nil;
+
+#include <Availability.h>
+#include <objc/message.h>
+
+// remove compilation warnings for strict builds by defining these selectors, even though
+// they are only ever used indirectly through objc_msgSend
+@interface GCExtendedGamepad (SDL)
+#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
+@property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
+@property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions;
+#endif
+#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 121000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1401000)
+@property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
+@property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
+#endif
+@end
+
+#define BUTTON_INDEX_A 0
+#define BUTTON_INDEX_B 1
+#define BUTTON_INDEX_X 2
+#define BUTTON_INDEX_Y 3
+#define BUTTON_INDEX_LEFT_SHOULDER 4
+#define BUTTON_INDEX_RIGHT_SHOULDER 5
+#define BUTTON_INDEX_GUIDE 6
+#define BUTTON_INDEX_LEFT_THUMBSTICK 7
+#define BUTTON_INDEX_RIGHT_THUMBSTICK 8
+#define BUTTON_INDEX_START 9
+#define BUTTON_INDEX_BACK 10
+
#endif /* SDL_JOYSTICK_MFI */
#if !TARGET_OS_TV
@@ -82,6 +111,8 @@ IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controlle
{
#ifdef SDL_JOYSTICK_MFI
const Uint16 VENDOR_APPLE = 0x05AC;
+ const Uint16 VENDOR_MICROSOFT = 0x045e;
+ const Uint16 VENDOR_SONY = 0x054C;
Uint16 *guid16 = (Uint16 *)device->guid.data;
Uint16 vendor = 0;
Uint16 product = 0;
@@ -104,12 +135,45 @@ IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCController *controlle
device->name = SDL_strdup(name);
if (controller.extendedGamepad) {
- vendor = VENDOR_APPLE;
- product = 1;
- subtype = 1;
+ int nbuttons = 7; /* ABXY, shoulder buttons, pause button */
+
+ if ([controller.extendedGamepad respondsToSelector:@selector(buttonMenu)]
+ && ((id (*)(id, SEL))objc_msgSend)(controller.extendedGamepad, @selector(buttonMenu))) {
+ // if we see .buttonMenu, then .buttonOption, .leftThumbstickButton (L3) & .rightThumbstickButton (R3)
+ // also exist (ios13+, macOS10.15+), though some may be nil, hold a spot for them
+ nbuttons = 11;
+ } else if ([controller.extendedGamepad respondsToSelector:@selector(leftThumbstickButton)]
+ && ((id (*)(id, SEL))objc_msgSend)(controller.extendedGamepad, @selector(leftThumbstickButton))) {
+ // if we didn't see .buttonMenu but do see .leftThumbstickButton (L3), then .rightThumbstickButton (R3)
+ // also exists (ios12.1+, macos10.14.1+). unlikely for R3 to be nil if L3 is not, but update code
+ // will never report a button change for R3 even so
+ nbuttons = 9;
+ }
+
+ if ([controller.vendorName containsString: @"Xbox"]) {
+ vendor = VENDOR_MICROSOFT;
+ product = 0x02E0; // assume Xbox One S BLE Controller unless/until GCController flows VID/PID
+ } else if ([controller.vendorName containsString: @"DUALSHOCK"]) {
+ vendor = VENDOR_SONY;
+ product = 0x09CC; // assume DS4 Slim unless/until GCController flows VID/PID
+ } else if (nbuttons == 9) {
+ // unknown MFi controller with L3/R3 buttons (e.g. Rotor Riot)
+ vendor = VENDOR_APPLE;
+ product = 4;
+ subtype = 4;
+ } else if (nbuttons == 11) {
+ // unkonwn MFi controller with L3/R3 and menu/options buttons (no known instances, future proofing)
+ vendor = VENDOR_APPLE;
+ product = 5;
+ subtype = 5;
+ } else {
+ vendor = VENDOR_APPLE;
+ product = 1;
+ subtype = 1;
+ }
device->naxes = 6; /* 2 thumbsticks and 2 triggers */
device->nhats = 1; /* d-pad */
- device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
+ device->nbuttons = nbuttons;
} else if (controller.gamepad) {
vendor = VENDOR_APPLE;
product = 2;
@@ -525,13 +589,25 @@ IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
};
/* Button order matches the XInput Windows mappings. */
- Uint8 buttons[] = {
- gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
- gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
- gamepad.leftShoulder.isPressed,
- gamepad.rightShoulder.isPressed,
- joystick->delayed_guide_button,
- };
+ Uint8 buttons[joystick->nbuttons];
+ buttons[BUTTON_INDEX_A] = gamepad.buttonA.isPressed;
+ buttons[BUTTON_INDEX_B] = gamepad.buttonB.isPressed;
+ buttons[BUTTON_INDEX_X] = gamepad.buttonX.isPressed;
+ buttons[BUTTON_INDEX_Y] = gamepad.buttonY.isPressed;
+ buttons[BUTTON_INDEX_LEFT_SHOULDER] = gamepad.leftShoulder.isPressed;
+ buttons[BUTTON_INDEX_RIGHT_SHOULDER] = gamepad.rightShoulder.isPressed;
+ buttons[BUTTON_INDEX_GUIDE] = joystick->delayed_guide_button;
+
+ // previously checked for availability of these iOS12.1+/macOS10.14.1+ or iOS13+/macOS10.15+
+ // selectors. they exist but may be nil, in which case objc_msgSend will return 0/false for isPressed
+ if (joystick->nbuttons > 8) {
+ buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(leftThumbstickButton)), @selector(isPressed) );
+ buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(rightThumbstickButton)), @selector(isPressed) );
+ }
+ if (joystick->nbuttons > 10) {
+ buttons[BUTTON_INDEX_START] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(buttonMenu)), @selector(isPressed) );
+ buttons[BUTTON_INDEX_BACK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(buttonOptions)), @selector(isPressed) );
+ }
hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
@@ -601,9 +677,8 @@ IOS_MFIJoystickUpdate(SDL_Joystick * joystick)
}
for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
- const Uint8 pausebutton = joystick->nbuttons - 1; /* The pause button is always last. */
- SDL_PrivateJoystickButton(joystick, pausebutton, SDL_PRESSED);
- SDL_PrivateJoystickButton(joystick, pausebutton, SDL_RELEASED);
+ SDL_PrivateJoystickButton(joystick, BUTTON_INDEX_GUIDE, SDL_PRESSED);
+ SDL_PrivateJoystickButton(joystick, BUTTON_INDEX_GUIDE, SDL_RELEASED);
updateplayerindex = YES;
}
joystick->hwdata->num_pause_presses = 0;