From 20ec8664845e5ee67807e33e4ffa1727224094ec Mon Sep 17 00:00:00 2001 From: Sam Lantinga Date: Thu, 6 Jun 2019 08:20:53 -0700 Subject: [PATCH] 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 | 4 + src/joystick/iphoneos/SDL_sysjoystick.m | 103 ++++++++++++++++++++---- 2 files changed, 93 insertions(+), 14 deletions(-) diff --git a/src/joystick/SDL_gamecontrollerdb.h b/src/joystick/SDL_gamecontrollerdb.h index c5bade9046b9e..0a6c091fbb464 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 4bb9395faa58b..d4b145ac09d15 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 +#include + +// 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 @@ { #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 @@ 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 @@ }; /* 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 @@ } 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;