Added support for Xbox and PS4 wireless controllers on iOS and tvOS
authorSam Lantinga <slouken@libsdl.org>
Fri, 14 Jun 2019 13:56:52 -0700
changeset 128617409d02471b3
parent 12860 0f52dd40abe5
child 12862 63a0d3c13f44
Added support for Xbox and PS4 wireless controllers on iOS and tvOS
Also implemented SDL_JoystickGetDevicePlayerIndex() on iOS and tvOS, and added support for reading the new menu button state available in iOS and tvOS 13.
src/joystick/SDL_gamecontrollerdb.h
src/joystick/iphoneos/SDL_sysjoystick.m
src/joystick/iphoneos/SDL_sysjoystick_c.h
     1.1 --- a/src/joystick/SDL_gamecontrollerdb.h	Fri Jun 14 13:56:42 2019 -0700
     1.2 +++ b/src/joystick/SDL_gamecontrollerdb.h	Fri Jun 14 13:56:52 2019 -0700
     1.3 @@ -377,6 +377,7 @@
     1.4      "030000006f0e00001302000000010000,Afterglow,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,",
     1.5      "03000000100000008200000011010000,Akishop Customs PS360+ v1.66,a:b1,b:b2,back:b12,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b0,y:b3,",
     1.6      "03000000b40400000a01000000010000,CYPRESS USB Gamepad,a:b0,b:b1,back:b5,guide:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b8,x:b3,y:b4,",
     1.7 +    "03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
     1.8      "03000000e82000006058000001010000,Cideko AK08b,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b3,y:b0,",
     1.9      "03000000260900008888000000010000,Cyber Gadget GameCube Controller,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,righttrigger:a5,rightx:a2,righty:a3~,start:b7,x:b2,y:b3,",
    1.10      "03000000a306000022f6000011010000,Cyborg V.3 Rumble Pad,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:+a3,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:-a3,rightx:a2,righty:a4,start:b9,x:b0,y:b3,",
    1.11 @@ -567,7 +568,6 @@
    1.12      "050000006964726f69643a636f6e0000,idroid:con,a:b1,b:b2,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b0,y:b3,",
    1.13      "03000000b50700001503000010010000,impact,a:b2,b:b3,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b9,x:b0,y:b1,",
    1.14      "030000009b2800000300000001010000,raphnet.net 4nes4snes v1.5,a:b0,b:b4,back:b2,leftshoulder:b6,leftx:a0,lefty:a1,rightshoulder:b7,start:b3,x:b1,y:b5,",
    1.15 -    "03000000ffff0000ffff000000010000,Chinese-made Xbox Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,",
    1.16  #endif
    1.17  #if defined(__ANDROID__)
    1.18      "05000000d6020000e5890000dfff3f00,GPD XD Plus,a:b0,b:b1,back:b4,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,leftshoulder:b9,leftstick:b7,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a4,rightx:a2,righty:a5,start:b6,x:b2,y:b3,",
    1.19 @@ -588,15 +588,14 @@
    1.20      "050000005e04000091020000ff073f00,Xbox Wireless Controller,a:b0,b:b1,back:b4,guide:b5,leftshoulder:b9,leftstick:b7,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b10,rightstick:b8,righttrigger:a5,rightx:a3,righty:a4,start:b6,x:b2,y:b3,", /* The DPAD doesn't seem to work on this controller on Android TV? */
    1.21  #endif
    1.22  #if defined(SDL_JOYSTICK_MFI)
    1.23 -    "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.24 -    "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.25 -    "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.26 -    "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.27 -    "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.28 -    "05000000ac0500000300000000006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,",
    1.29 +    "05000000ac050000010000004f066d01,*,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.30 +    "05000000ac05000001000000cf076d01,*,a:b0,b:b1,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,x:b2,y:b3,",
    1.31 +    "05000000ac050000020000004f066d02,*,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.32 +    "050000004c050000cc090000df070000,DUALSHOCK 4 Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
    1.33 +    "05000000ac0500000300000043006d03,Remote,a:b0,b:b2,leftx:a0,lefty:a1,",
    1.34      "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.35      "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.36 -    "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.37 +    "050000005e040000e0020000df070000,Xbox Wireless Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b6,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b7,righttrigger:a5,rightx:a3,righty:a4,start:b9,x:b2,y:b3,",
    1.38  #endif
    1.39  #if defined(SDL_JOYSTICK_EMSCRIPTEN)
    1.40      "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	Fri Jun 14 13:56:42 2019 -0700
     2.2 +++ b/src/joystick/iphoneos/SDL_sysjoystick.m	Fri Jun 14 13:56:52 2019 -0700
     2.3 @@ -52,31 +52,25 @@
     2.4  #include <Availability.h>
     2.5  #include <objc/message.h>
     2.6  
     2.7 -// remove compilation warnings for strict builds by defining these selectors, even though
     2.8 -// they are only ever used indirectly through objc_msgSend
     2.9 +/* remove compilation warnings for strict builds by defining these selectors, even though
    2.10 + * they are only ever used indirectly through objc_msgSend
    2.11 + */
    2.12  @interface GCExtendedGamepad (SDL)
    2.13 -#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
    2.14 +#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 121000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 121000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1401000)
    2.15 +@property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
    2.16 +@property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
    2.17 +#endif
    2.18 +#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
    2.19  @property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
    2.20  @property (nonatomic, readonly, nullable) GCControllerButtonInput *buttonOptions;
    2.21  #endif
    2.22 -#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 121000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1401000)
    2.23 -@property (nonatomic, readonly, nullable) GCControllerButtonInput *leftThumbstickButton;
    2.24 -@property (nonatomic, readonly, nullable) GCControllerButtonInput *rightThumbstickButton;
    2.25 +@end
    2.26 +@interface GCMicroGamepad (SDL)
    2.27 +#if (__IPHONE_OS_VERSION_MAX_ALLOWED < 130000) || (__APPLETV_OS_VERSION_MAX_ALLOWED < 130000) || (__MAC_OS_VERSION_MAX_ALLOWED < 1500000)
    2.28 +@property (nonatomic, readonly) GCControllerButtonInput *buttonMenu;
    2.29  #endif
    2.30  @end
    2.31  
    2.32 -#define BUTTON_INDEX_A  0
    2.33 -#define BUTTON_INDEX_B  1
    2.34 -#define BUTTON_INDEX_X  2
    2.35 -#define BUTTON_INDEX_Y  3
    2.36 -#define BUTTON_INDEX_LEFT_SHOULDER  4
    2.37 -#define BUTTON_INDEX_RIGHT_SHOULDER  5
    2.38 -#define BUTTON_INDEX_GUIDE  6
    2.39 -#define BUTTON_INDEX_LEFT_THUMBSTICK  7
    2.40 -#define BUTTON_INDEX_RIGHT_THUMBSTICK  8
    2.41 -#define BUTTON_INDEX_START  9
    2.42 -#define BUTTON_INDEX_BACK  10
    2.43 -
    2.44  #endif /* SDL_JOYSTICK_MFI */
    2.45  
    2.46  #if !TARGET_OS_TV
    2.47 @@ -116,7 +110,6 @@
    2.48      Uint16 *guid16 = (Uint16 *)device->guid.data;
    2.49      Uint16 vendor = 0;
    2.50      Uint16 product = 0;
    2.51 -    Uint16 version = 0;
    2.52      Uint8 subtype = 0;
    2.53  
    2.54      const char *name = NULL;
    2.55 @@ -135,61 +128,104 @@
    2.56      device->name = SDL_strdup(name);
    2.57  
    2.58      if (controller.extendedGamepad) {
    2.59 -        int nbuttons = 7; /* ABXY, shoulder buttons, pause button */
    2.60 +        GCExtendedGamepad *gamepad = controller.extendedGamepad;
    2.61 +        int nbuttons = 0;
    2.62  
    2.63 -        if ([controller.extendedGamepad respondsToSelector:@selector(buttonMenu)]
    2.64 -            && ((id (*)(id, SEL))objc_msgSend)(controller.extendedGamepad, @selector(buttonMenu))) {
    2.65 -            // if we see .buttonMenu, then .buttonOption, .leftThumbstickButton (L3) & .rightThumbstickButton (R3)
    2.66 -            // also exist (ios13+, macOS10.15+), though some may be nil, hold a spot for them
    2.67 -            nbuttons = 11;
    2.68 -        } else if ([controller.extendedGamepad respondsToSelector:@selector(leftThumbstickButton)]
    2.69 -                   && ((id (*)(id, SEL))objc_msgSend)(controller.extendedGamepad, @selector(leftThumbstickButton))) {
    2.70 -            // if we didn't see .buttonMenu but do see .leftThumbstickButton (L3), then .rightThumbstickButton (R3) 
    2.71 -            // also exists (ios12.1+, macos10.14.1+). unlikely for R3 to be nil if L3 is not, but update code
    2.72 -            // will never report a button change for R3 even so
    2.73 -            nbuttons = 9;
    2.74 +        /* These buttons are part of the original MFi spec */
    2.75 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
    2.76 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
    2.77 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
    2.78 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
    2.79 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
    2.80 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
    2.81 +        nbuttons += 6;
    2.82 +
    2.83 +        /* These buttons are available on some newer controllers */
    2.84 +#pragma clang diagnostic push
    2.85 +#pragma clang diagnostic ignored "-Wunguarded-availability-new"
    2.86 +        if ([gamepad respondsToSelector:@selector(leftThumbstickButton)] && gamepad.leftThumbstickButton) {
    2.87 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK);
    2.88 +            ++nbuttons;
    2.89          }
    2.90 +        if ([gamepad respondsToSelector:@selector(rightThumbstickButton)] && gamepad.rightThumbstickButton) {
    2.91 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK);
    2.92 +            ++nbuttons;
    2.93 +        }
    2.94 +        if ([gamepad respondsToSelector:@selector(buttonOptions)] && gamepad.buttonOptions) {
    2.95 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_BACK);
    2.96 +            ++nbuttons;
    2.97 +        }
    2.98 +        if ([gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu) {
    2.99 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   2.100 +            ++nbuttons;
   2.101 +        } else {
   2.102 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   2.103 +            ++nbuttons;
   2.104 +            device->uses_pause_handler = SDL_TRUE;
   2.105 +        }
   2.106 +#pragma clang diagnostic pop
   2.107  
   2.108          if ([controller.vendorName containsString: @"Xbox"]) {
   2.109              vendor = VENDOR_MICROSOFT;
   2.110 -            product = 0x02E0; // assume Xbox One S BLE Controller unless/until GCController flows VID/PID
   2.111 +            product = 0x02E0; /* Assume Xbox One S BLE Controller unless/until GCController flows VID/PID */
   2.112          } else if ([controller.vendorName containsString: @"DUALSHOCK"]) {
   2.113              vendor = VENDOR_SONY;
   2.114 -            product = 0x09CC; // assume DS4 Slim unless/until GCController flows VID/PID
   2.115 -        } else if (nbuttons == 9) {
   2.116 -            // unknown MFi controller with L3/R3 buttons (e.g. Rotor Riot)
   2.117 -            vendor = VENDOR_APPLE;
   2.118 -            product = 4;
   2.119 -            subtype = 4;
   2.120 -        } else if (nbuttons == 11) {
   2.121 -            // unkonwn MFi controller with L3/R3 and menu/options buttons (no known instances, future proofing)
   2.122 -            vendor = VENDOR_APPLE;
   2.123 -            product = 5;
   2.124 -            subtype = 5;
   2.125 +            product = 0x09CC; /* Assume DS4 Slim unless/until GCController flows VID/PID */
   2.126          } else {
   2.127              vendor = VENDOR_APPLE;
   2.128              product = 1;
   2.129              subtype = 1;
   2.130          }
   2.131 +
   2.132          device->naxes = 6; /* 2 thumbsticks and 2 triggers */
   2.133          device->nhats = 1; /* d-pad */
   2.134          device->nbuttons = nbuttons;
   2.135 +
   2.136      } else if (controller.gamepad) {
   2.137 +        int nbuttons = 0;
   2.138 +
   2.139 +        /* These buttons are part of the original MFi spec */
   2.140 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
   2.141 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B);
   2.142 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_X);
   2.143 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_Y);
   2.144 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_LEFTSHOULDER);
   2.145 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_RIGHTSHOULDER);
   2.146 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   2.147 +        nbuttons += 7;
   2.148 +        device->uses_pause_handler = SDL_TRUE;
   2.149 +
   2.150          vendor = VENDOR_APPLE;
   2.151          product = 2;
   2.152          subtype = 2;
   2.153          device->naxes = 0; /* no traditional analog inputs */
   2.154          device->nhats = 1; /* d-pad */
   2.155 -        device->nbuttons = 7; /* ABXY, shoulder buttons, pause button */
   2.156 +        device->nbuttons = nbuttons;
   2.157      }
   2.158  #if TARGET_OS_TV
   2.159      else if (controller.microGamepad) {
   2.160 +        GCMicroGamepad *gamepad = controller.microGamepad;
   2.161 +        int nbuttons = 0;
   2.162 +
   2.163 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_A);
   2.164 +        device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_B); /* Button X on microGamepad */
   2.165 +        nbuttons += 2;
   2.166 +
   2.167 +        if ([gamepad respondsToSelector:@selector(buttonMenu)] && gamepad.buttonMenu) {
   2.168 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   2.169 +            ++nbuttons;
   2.170 +        } else {
   2.171 +            device->button_mask |= (1 << SDL_CONTROLLER_BUTTON_START);
   2.172 +            ++nbuttons;
   2.173 +            device->uses_pause_handler = SDL_TRUE;
   2.174 +        }
   2.175 +
   2.176          vendor = VENDOR_APPLE;
   2.177          product = 3;
   2.178          subtype = 3;
   2.179          device->naxes = 2; /* treat the touch surface as two axes */
   2.180          device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
   2.181 -        device->nbuttons = 3; /* AX, pause button */
   2.182 +        device->nbuttons = nbuttons;
   2.183  
   2.184          controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
   2.185      }
   2.186 @@ -203,12 +239,14 @@
   2.187      *guid16++ = 0;
   2.188      *guid16++ = SDL_SwapLE16(product);
   2.189      *guid16++ = 0;
   2.190 -    *guid16++ = SDL_SwapLE16(version);
   2.191 -    *guid16++ = 0;
   2.192  
   2.193 -    /* Note that this is an MFI controller and what subtype it is */
   2.194 -    device->guid.data[14] = 'm';
   2.195 -    device->guid.data[15] = subtype;
   2.196 +    *guid16++ = SDL_SwapLE16(device->button_mask);
   2.197 +
   2.198 +    if (subtype != 0) {
   2.199 +        /* Note that this is an MFI controller and what subtype it is */
   2.200 +        device->guid.data[14] = 'm';
   2.201 +        device->guid.data[15] = subtype;
   2.202 +    }
   2.203  
   2.204      /* This will be set when the first button press of the controller is
   2.205       * detected. */
   2.206 @@ -425,7 +463,8 @@
   2.207  static int
   2.208  IOS_JoystickGetDevicePlayerIndex(int device_index)
   2.209  {
   2.210 -    return -1;
   2.211 +    SDL_JoystickDeviceItem *device = GetDeviceForIndex(device_index);
   2.212 +    return device ? (int)device->controller.playerIndex : -1;
   2.213  }
   2.214  
   2.215  static SDL_JoystickGUID
   2.216 @@ -479,12 +518,14 @@
   2.217  #endif /* !TARGET_OS_TV */
   2.218          } else {
   2.219  #ifdef SDL_JOYSTICK_MFI
   2.220 -            GCController *controller = device->controller;
   2.221 -            controller.controllerPausedHandler = ^(GCController *c) {
   2.222 -                if (joystick->hwdata) {
   2.223 -                    ++joystick->hwdata->num_pause_presses;
   2.224 -                }
   2.225 -            };
   2.226 +            if (device->uses_pause_handler) {
   2.227 +                GCController *controller = device->controller;
   2.228 +                controller.controllerPausedHandler = ^(GCController *c) {
   2.229 +                    if (joystick->hwdata) {
   2.230 +                        ++joystick->hwdata->num_pause_presses;
   2.231 +                    }
   2.232 +                };
   2.233 +            }
   2.234  #endif /* SDL_JOYSTICK_MFI */
   2.235          }
   2.236      }
   2.237 @@ -574,6 +615,7 @@
   2.238          Uint8 hatstate = SDL_HAT_CENTERED;
   2.239          int i;
   2.240          int updateplayerindex = 0;
   2.241 +        int pause_button_index = 0;
   2.242  
   2.243          if (controller.extendedGamepad) {
   2.244              GCExtendedGamepad *gamepad = controller.extendedGamepad;
   2.245 @@ -590,24 +632,37 @@
   2.246  
   2.247              /* Button order matches the XInput Windows mappings. */
   2.248              Uint8 buttons[joystick->nbuttons];
   2.249 -            buttons[BUTTON_INDEX_A] = gamepad.buttonA.isPressed;
   2.250 -            buttons[BUTTON_INDEX_B] = gamepad.buttonB.isPressed;
   2.251 -            buttons[BUTTON_INDEX_X] = gamepad.buttonX.isPressed;
   2.252 -            buttons[BUTTON_INDEX_Y] = gamepad.buttonY.isPressed;
   2.253 -            buttons[BUTTON_INDEX_LEFT_SHOULDER] = gamepad.leftShoulder.isPressed;
   2.254 -            buttons[BUTTON_INDEX_RIGHT_SHOULDER] = gamepad.rightShoulder.isPressed;
   2.255 -            buttons[BUTTON_INDEX_GUIDE] = joystick->delayed_guide_button;
   2.256 +            int button_count = 0;
   2.257  
   2.258 -            // previously checked for availability of these iOS12.1+/macOS10.14.1+ or iOS13+/macOS10.15+
   2.259 -            // selectors. they exist but may be nil, in which case objc_msgSend will return 0/false for isPressed
   2.260 -            if (joystick->nbuttons > 8) {
   2.261 -                buttons[BUTTON_INDEX_LEFT_THUMBSTICK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(leftThumbstickButton)), @selector(isPressed) );
   2.262 -                buttons[BUTTON_INDEX_RIGHT_THUMBSTICK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(rightThumbstickButton)), @selector(isPressed) );
   2.263 +            /* These buttons are part of the original MFi spec */
   2.264 +            buttons[button_count++] = gamepad.buttonA.isPressed;
   2.265 +            buttons[button_count++] = gamepad.buttonB.isPressed;
   2.266 +            buttons[button_count++] = gamepad.buttonX.isPressed;
   2.267 +            buttons[button_count++] = gamepad.buttonY.isPressed;
   2.268 +            buttons[button_count++] = gamepad.leftShoulder.isPressed;
   2.269 +            buttons[button_count++] = gamepad.rightShoulder.isPressed;
   2.270 +
   2.271 +            /* These buttons are available on some newer controllers */
   2.272 +#pragma clang diagnostic push
   2.273 +#pragma clang diagnostic ignored "-Wunguarded-availability-new"
   2.274 +            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_LEFTSTICK)) {
   2.275 +                buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
   2.276              }
   2.277 -            if (joystick->nbuttons > 10) {
   2.278 -                buttons[BUTTON_INDEX_START] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(buttonMenu)), @selector(isPressed) );
   2.279 -                buttons[BUTTON_INDEX_BACK] = ((Uint8 (*)(id, SEL))objc_msgSend)( ((id (*)(id, SEL))objc_msgSend)(gamepad, @selector(buttonOptions)), @selector(isPressed) );
   2.280 +            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_RIGHTSTICK)) {
   2.281 +                buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
   2.282              }
   2.283 +            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_BACK)) {
   2.284 +                buttons[button_count++] = gamepad.buttonOptions.isPressed;
   2.285 +            }
   2.286 +            /* This must be the last button, so we can optionally handle it with pause_button_index below */
   2.287 +            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
   2.288 +                if (joystick->hwdata->uses_pause_handler) {
   2.289 +                    pause_button_index = button_count;
   2.290 +                } else {
   2.291 +                    buttons[button_count++] = gamepad.buttonMenu.isPressed;
   2.292 +                }
   2.293 +            }
   2.294 +#pragma clang diagnostic pop
   2.295  
   2.296              hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
   2.297  
   2.298 @@ -621,7 +676,7 @@
   2.299                  SDL_PrivateJoystickAxis(joystick, i, axes[i]);
   2.300              }
   2.301  
   2.302 -            for (i = 0; i < SDL_arraysize(buttons); i++) {
   2.303 +            for (i = 0; i < button_count; i++) {
   2.304                  updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   2.305                  SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   2.306              }
   2.307 @@ -629,17 +684,19 @@
   2.308              GCGamepad *gamepad = controller.gamepad;
   2.309  
   2.310              /* Button order matches the XInput Windows mappings. */
   2.311 -            Uint8 buttons[] = {
   2.312 -                gamepad.buttonA.isPressed, gamepad.buttonB.isPressed,
   2.313 -                gamepad.buttonX.isPressed, gamepad.buttonY.isPressed,
   2.314 -                gamepad.leftShoulder.isPressed,
   2.315 -                gamepad.rightShoulder.isPressed,
   2.316 -                joystick->delayed_guide_button,
   2.317 -            };
   2.318 +            Uint8 buttons[joystick->nbuttons];
   2.319 +            int button_count = 0;
   2.320 +            buttons[button_count++] = gamepad.buttonA.isPressed;
   2.321 +            buttons[button_count++] = gamepad.buttonB.isPressed;
   2.322 +            buttons[button_count++] = gamepad.buttonX.isPressed;
   2.323 +            buttons[button_count++] = gamepad.buttonY.isPressed;
   2.324 +            buttons[button_count++] = gamepad.leftShoulder.isPressed;
   2.325 +            buttons[button_count++] = gamepad.rightShoulder.isPressed;
   2.326 +            pause_button_index = button_count;
   2.327  
   2.328              hatstate = IOS_MFIJoystickHatStateForDPad(gamepad.dpad);
   2.329  
   2.330 -            for (i = 0; i < SDL_arraysize(buttons); i++) {
   2.331 +            for (i = 0; i < button_count; i++) {
   2.332                  updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   2.333                  SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   2.334              }
   2.335 @@ -658,13 +715,23 @@
   2.336                  SDL_PrivateJoystickAxis(joystick, i, axes[i]);
   2.337              }
   2.338  
   2.339 -            Uint8 buttons[] = {
   2.340 -                gamepad.buttonA.isPressed,
   2.341 -                gamepad.buttonX.isPressed,
   2.342 -                joystick->delayed_guide_button,
   2.343 -            };
   2.344 +            Uint8 buttons[joystick->nbuttons];
   2.345 +            int button_count = 0;
   2.346 +            buttons[button_count++] = gamepad.buttonA.isPressed;
   2.347 +            buttons[button_count++] = gamepad.buttonX.isPressed;
   2.348 +#pragma clang diagnostic push
   2.349 +#pragma clang diagnostic ignored "-Wunguarded-availability-new"
   2.350 +            /* This must be the last button, so we can optionally handle it with pause_button_index below */
   2.351 +            if (joystick->hwdata->button_mask & (1 << SDL_CONTROLLER_BUTTON_START)) {
   2.352 +                if (joystick->hwdata->uses_pause_handler) {
   2.353 +                    pause_button_index = button_count;
   2.354 +                } else {
   2.355 +                    buttons[button_count++] = gamepad.buttonMenu.isPressed;
   2.356 +                }
   2.357 +            }
   2.358 +#pragma clang diagnostic pop
   2.359  
   2.360 -            for (i = 0; i < SDL_arraysize(buttons); i++) {
   2.361 +            for (i = 0; i < button_count; i++) {
   2.362                  updateplayerindex |= (joystick->buttons[i] != buttons[i]);
   2.363                  SDL_PrivateJoystickButton(joystick, i, buttons[i]);
   2.364              }
   2.365 @@ -676,12 +743,14 @@
   2.366              SDL_PrivateJoystickHat(joystick, 0, hatstate);
   2.367          }
   2.368  
   2.369 -        for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   2.370 -            SDL_PrivateJoystickButton(joystick, BUTTON_INDEX_GUIDE, SDL_PRESSED);
   2.371 -            SDL_PrivateJoystickButton(joystick, BUTTON_INDEX_GUIDE, SDL_RELEASED);
   2.372 -            updateplayerindex = YES;
   2.373 +        if (joystick->hwdata->uses_pause_handler) {
   2.374 +            for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
   2.375 +                SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_PRESSED);
   2.376 +                SDL_PrivateJoystickButton(joystick, pause_button_index, SDL_RELEASED);
   2.377 +                updateplayerindex = YES;
   2.378 +            }
   2.379 +            joystick->hwdata->num_pause_presses = 0;
   2.380          }
   2.381 -        joystick->hwdata->num_pause_presses = 0;
   2.382  
   2.383          if (updateplayerindex && controller.playerIndex == -1) {
   2.384              BOOL usedPlayerIndexSlots[4] = {NO, NO, NO, NO};
     3.1 --- a/src/joystick/iphoneos/SDL_sysjoystick_c.h	Fri Jun 14 13:56:42 2019 -0700
     3.2 +++ b/src/joystick/iphoneos/SDL_sysjoystick_c.h	Fri Jun 14 13:56:52 2019 -0700
     3.3 @@ -34,6 +34,7 @@
     3.4      SDL_bool remote;
     3.5  
     3.6      GCController __unsafe_unretained *controller;
     3.7 +    SDL_bool uses_pause_handler;
     3.8      int num_pause_presses;
     3.9      Uint32 pause_button_down_time;
    3.10  
    3.11 @@ -45,6 +46,7 @@
    3.12      int naxes;
    3.13      int nbuttons;
    3.14      int nhats;
    3.15 +    Uint16 button_mask;
    3.16  
    3.17      struct joystick_hwdata *next;
    3.18  } joystick_hwdata;