Skip to content

Commit

Permalink
Added support for the Rotor Riot gamepad, and upcoming Xbox and PS4 c…
Browse files Browse the repository at this point in the history
…ontroller support on iOS and tvOS

Patch contributed by Nat Brown
  • Loading branch information
slouken committed Jun 6, 2019
1 parent 6c0b304 commit 20ec866
Show file tree
Hide file tree
Showing 2 changed files with 93 additions and 14 deletions.
4 changes: 4 additions & 0 deletions src/joystick/SDL_gamecontrollerdb.h
Expand Up @@ -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,",
Expand Down
103 changes: 89 additions & 14 deletions src/joystick/iphoneos/SDL_sysjoystick.m
Expand Up @@ -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
Expand Down Expand Up @@ -82,6 +111,8 @@
{
#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;
Expand All @@ -104,12 +135,45 @@
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;
Expand Down Expand Up @@ -525,13 +589,25 @@
};

/* 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);

Expand Down Expand Up @@ -601,9 +677,8 @@
}

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;
Expand Down

0 comments on commit 20ec866

Please sign in to comment.