From f5600fd9f4f0f0bcde3ee1e54574fa6ab0a4f585 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Tue, 14 Nov 2023 06:28:27 -0800
Subject: [PATCH] Fall back to using the physical profile for Apple controllers
if they don't match a standard profile
We may want to flip this to be the default, but it needs more testing.
---
src/joystick/apple/SDL_mfijoystick.m | 299 +++++++++++++++----------
src/joystick/apple/SDL_mfijoystick_c.h | 7 +
2 files changed, 183 insertions(+), 123 deletions(-)
diff --git a/src/joystick/apple/SDL_mfijoystick.m b/src/joystick/apple/SDL_mfijoystick.m
index 1d45799e1650..e53ddd01fa2b 100644
--- a/src/joystick/apple/SDL_mfijoystick.m
+++ b/src/joystick/apple/SDL_mfijoystick.m
@@ -271,32 +271,116 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
}
#endif
+ BOOL is_xbox = IsControllerXbox(controller);
+ BOOL is_ps4 = IsControllerPS4(controller);
+ BOOL is_ps5 = IsControllerPS5(controller);
+ BOOL is_switch_pro = IsControllerSwitchPro(controller);
+ BOOL is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller);
+ BOOL is_stadia = IsControllerStadia(controller);
+ BOOL is_backbone_one = IsControllerBackboneOne(controller);
+ BOOL is_switch_joyconL = IsControllerSwitchJoyConL(controller);
+ BOOL is_switch_joyconR = IsControllerSwitchJoyConR(controller);
+#ifdef SDL_JOYSTICK_HIDAPI
+ if ((is_xbox && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE)) ||
+ (is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) ||
+ (is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) ||
+ (is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) ||
+ (is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) ||
+ (is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, "")) ||
+ (is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) ||
+ (is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) {
+ /* The HIDAPI driver is taking care of this device */
+ return FALSE;
+ }
+#else
+ (void)is_stadia;
+ (void)is_switch_joyconL;
+ (void)is_switch_joyconR;
+#endif
+
+ if (is_backbone_one) {
+ vendor = USB_VENDOR_BACKBONE;
+ if (is_ps5) {
+ product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5;
+ } else {
+ product = USB_PRODUCT_BACKBONE_ONE_IOS;
+ }
+ subtype = 0;
+ } else if (is_xbox) {
+ vendor = USB_VENDOR_MICROSOFT;
+ if (device->has_xbox_paddles) {
+ /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */
+ product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH;
+ subtype = 1;
+ } else if (device->has_xbox_share_button) {
+ /* Assume Xbox Series X Controller unless/until GCController flows VID/PID */
+ product = USB_PRODUCT_XBOX_SERIES_X_BLE;
+ subtype = 1;
+ } else {
+ /* Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID */
+ product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
+ subtype = 0;
+ }
+ } else if (is_ps4) {
+ /* Assume DS4 Slim unless/until GCController flows VID/PID */
+ vendor = USB_VENDOR_SONY;
+ product = USB_PRODUCT_SONY_DS4_SLIM;
+ if (device->has_dualshock_touchpad) {
+ subtype = 1;
+ } else {
+ subtype = 0;
+ }
+ } else if (is_ps5) {
+ vendor = USB_VENDOR_SONY;
+ product = USB_PRODUCT_SONY_DS5;
+ subtype = 0;
+ } else if (is_switch_pro) {
+ vendor = USB_VENDOR_NINTENDO;
+ product = USB_PRODUCT_NINTENDO_SWITCH_PRO;
+ subtype = 0;
+ } else if (is_switch_joycon_pair) {
+ vendor = USB_VENDOR_NINTENDO;
+ product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
+ subtype = 0;
+ } else if (is_switch_joyconL) {
+ vendor = USB_VENDOR_NINTENDO;
+ product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT;
+ subtype = 0;
+ } else if (is_switch_joyconR) {
+ vendor = USB_VENDOR_NINTENDO;
+ product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT;
+ subtype = 0;
+ } else if (controller.extendedGamepad) {
+ vendor = USB_VENDOR_APPLE;
+ product = 1;
+ subtype = 1;
+ } else if (controller.gamepad) {
+ vendor = USB_VENDOR_APPLE;
+ product = 2;
+ subtype = 2;
+#if TARGET_OS_TV
+ } else if (controller.microGamepad) {
+ vendor = USB_VENDOR_APPLE;
+ product = 3;
+ subtype = 3;
+#endif
+#ifdef ENABLE_PHYSICAL_INPUT_PROFILE
+ } else if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
+ vendor = USB_VENDOR_APPLE;
+ product = 4;
+ subtype = 4;
+#endif
+ } else {
+ vendor = USB_VENDOR_APPLE;
+ product = 5;
+ subtype = 5;
+ }
+
if (controller.extendedGamepad) {
GCExtendedGamepad *gamepad = controller.extendedGamepad;
- BOOL is_xbox = IsControllerXbox(controller);
- BOOL is_ps4 = IsControllerPS4(controller);
- BOOL is_ps5 = IsControllerPS5(controller);
- BOOL is_switch_pro = IsControllerSwitchPro(controller);
- BOOL is_switch_joycon_pair = IsControllerSwitchJoyConPair(controller);
- BOOL is_stadia = IsControllerStadia(controller);
- BOOL is_backbone_one = IsControllerBackboneOne(controller);
int nbuttons = 0;
BOOL has_direct_menu;
-#ifdef SDL_JOYSTICK_HIDAPI
- if ((is_xbox && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_XBOXONE)) ||
- (is_ps4 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS4)) ||
- (is_ps5 && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_PS5)) ||
- (is_switch_pro && HIDAPI_IsDeviceTypePresent(SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO)) ||
- (is_switch_joycon_pair && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR, 0, "")) ||
- (is_stadia && HIDAPI_IsDevicePresent(USB_VENDOR_GOOGLE, USB_PRODUCT_GOOGLE_STADIA_CONTROLLER, 0, ""))) {
- /* The HIDAPI driver is taking care of this device */
- return FALSE;
- }
-#else
- (void)is_stadia;
-#endif
-
/* These buttons are part of the original MFi spec */
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
@@ -378,56 +462,6 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
#endif
#pragma clang diagnostic pop
- if (is_backbone_one) {
- vendor = USB_VENDOR_BACKBONE;
- if (is_ps5) {
- product = USB_PRODUCT_BACKBONE_ONE_IOS_PS5;
- } else {
- product = USB_PRODUCT_BACKBONE_ONE_IOS;
- }
- subtype = 0;
- } else if (is_xbox) {
- vendor = USB_VENDOR_MICROSOFT;
- if (device->has_xbox_paddles) {
- /* Assume Xbox One Elite Series 2 Controller unless/until GCController flows VID/PID */
- product = USB_PRODUCT_XBOX_ONE_ELITE_SERIES_2_BLUETOOTH;
- subtype = 1;
- } else if (device->has_xbox_share_button) {
- /* Assume Xbox Series X Controller unless/until GCController flows VID/PID */
- product = USB_PRODUCT_XBOX_SERIES_X_BLE;
- subtype = 1;
- } else {
- /* Assume Xbox One S Bluetooth Controller unless/until GCController flows VID/PID */
- product = USB_PRODUCT_XBOX_ONE_S_REV1_BLUETOOTH;
- subtype = 0;
- }
- } else if (is_ps4) {
- /* Assume DS4 Slim unless/until GCController flows VID/PID */
- vendor = USB_VENDOR_SONY;
- product = USB_PRODUCT_SONY_DS4_SLIM;
- if (device->has_dualshock_touchpad) {
- subtype = 1;
- } else {
- subtype = 0;
- }
- } else if (is_ps5) {
- vendor = USB_VENDOR_SONY;
- product = USB_PRODUCT_SONY_DS5;
- subtype = 0;
- } else if (is_switch_pro) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_PRO;
- subtype = 0;
- } else if (is_switch_joycon_pair) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_PAIR;
- subtype = 0;
- } else {
- vendor = USB_VENDOR_APPLE;
- product = 1;
- subtype = 1;
- }
-
if (is_backbone_one) {
/* The Backbone app uses share button */
if ((device->button_mask & (1 << SDL_GAMEPAD_BUTTON_MISC1)) != 0) {
@@ -442,35 +476,8 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
device->nbuttons = nbuttons;
} else if (controller.gamepad) {
- BOOL is_switch_joyconL = IsControllerSwitchJoyConL(controller);
- BOOL is_switch_joyconR = IsControllerSwitchJoyConR(controller);
int nbuttons = 0;
-#ifdef SDL_JOYSTICK_HIDAPI
- if ((is_switch_joyconL && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT, 0, "")) ||
- (is_switch_joyconR && HIDAPI_IsDevicePresent(USB_VENDOR_NINTENDO, USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT, 0, ""))) {
- /* The HIDAPI driver is taking care of this device */
- return FALSE;
- }
-#else
- (void)is_switch_joyconL;
- (void)is_switch_joyconR;
-#endif
-
- if (is_switch_joyconL) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_LEFT;
- subtype = 0;
- } else if (is_switch_joyconR) {
- vendor = USB_VENDOR_NINTENDO;
- product = USB_PRODUCT_NINTENDO_SWITCH_JOYCON_RIGHT;
- subtype = 0;
- } else {
- vendor = USB_VENDOR_APPLE;
- product = 2;
- subtype = 2;
- }
-
/* These buttons are part of the original MFi spec */
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_SOUTH);
device->button_mask |= (1 << SDL_GAMEPAD_BUTTON_EAST);
@@ -503,16 +510,29 @@ static BOOL IOS_AddMFIJoystickDevice(SDL_JoystickDeviceItem *device, GCControlle
++nbuttons;
device->uses_pause_handler = SDL_TRUE;
- vendor = USB_VENDOR_APPLE;
- product = 3;
- subtype = 3;
device->naxes = 2; /* treat the touch surface as two axes */
device->nhats = 0; /* apparently the touch surface-as-dpad is buggy */
device->nbuttons = nbuttons;
controller.microGamepad.allowsRotation = SDL_GetHintBoolean(SDL_HINT_APPLE_TV_REMOTE_ALLOW_ROTATION, SDL_FALSE);
}
-#endif /* TARGET_OS_TV */
+#endif
+#ifdef ENABLE_PHYSICAL_INPUT_PROFILE
+ else if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
+ device->use_physical_profile = SDL_TRUE;
+ device->axes = [controller.physicalInputProfile.axes allKeys];
+ device->naxes = controller.physicalInputProfile.axes.count;
+ device->dpads = [controller.physicalInputProfile.dpads allKeys];
+ device->nhats = controller.physicalInputProfile.dpads.count;
+ device->buttons = [controller.physicalInputProfile.buttons allKeys];
+ device->nbuttons = controller.physicalInputProfile.buttons.count;
+ subtype = 4;
+ }
+#endif
+ else {
+ /* We can't detect any inputs on this */
+ return SDL_FALSE;
+ }
if (vendor == USB_VENDOR_APPLE) {
/* Note that this is an MFI controller and what subtype it is */
@@ -957,7 +977,8 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
{
#ifdef SDL_JOYSTICK_MFI
@autoreleasepool {
- GCController *controller = joystick->hwdata->controller;
+ SDL_JoystickDeviceItem *device = joystick->hwdata;
+ GCController *controller = device->controller;
Uint8 hatstate = SDL_HAT_CENTERED;
int i;
int pause_button_index = 0;
@@ -976,11 +997,39 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
if (axis.value != 0.0f)
NSLog(@"Axis %@ = %g\n", key, axis.value);
}
+ for (id key in controller.physicalInputProfile.dpads) {
+ GCControllerDirectionPad *dpad = controller.physicalInputProfile.dpads[key];
+ if (dpad.up.isPressed || dpad.down.isPressed || dpad.left.isPressed || dpad.right.isPressed) {
+ NSLog(@"Hat %@ =%s%s%s%s\n", key,
+ dpad.up.isPressed ? " UP" : "",
+ dpad.down.isPressed ? " DOWN" : "",
+ dpad.left.isPressed ? " LEFT" : "",
+ dpad.right.isPressed ? " RIGHT" : "");
+ }
+ }
}
}
#endif
- if (controller.extendedGamepad) {
+ if (device->use_physical_profile) {
+ int axis = 0;
+ for (id key in device->axes) {
+ Sint16 value = (Sint16)(controller.physicalInputProfile.axes[key].value * 32767);
+ SDL_SendJoystickAxis(timestamp, joystick, axis++, value);
+ }
+
+ int button = 0;
+ for (id key in device->buttons) {
+ Uint8 value = controller.physicalInputProfile.buttons[key].isPressed;
+ SDL_SendJoystickButton(timestamp, joystick, button++, value);
+ }
+
+ int hat = 0;
+ for (id key in device->dpads) {
+ hatstate = IOS_MFIJoystickHatStateForDPad(controller.physicalInputProfile.dpads[key]);
+ SDL_SendJoystickHat(timestamp, joystick, hat++, hatstate);
+ }
+ } else if (controller.extendedGamepad) {
SDL_bool isstack;
GCExtendedGamepad *gamepad = controller.extendedGamepad;
@@ -1014,21 +1063,21 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
/* These buttons are available on some newer controllers */
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_STICK)) {
buttons[button_count++] = gamepad.leftThumbstickButton.isPressed;
}
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_STICK)) {
buttons[button_count++] = gamepad.rightThumbstickButton.isPressed;
}
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_BACK)) {
buttons[button_count++] = gamepad.buttonOptions.isPressed;
}
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_GUIDE)) {
buttons[button_count++] = gamepad.buttonHome.isPressed;
}
/* This must be the last button, so we can optionally handle it with pause_button_index below */
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
- if (joystick->hwdata->uses_pause_handler) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
+ if (device->uses_pause_handler) {
pause_button_index = button_count;
buttons[button_count++] = joystick->delayed_guide_button;
} else {
@@ -1037,7 +1086,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
#ifdef ENABLE_PHYSICAL_INPUT_PROFILE
- if (joystick->hwdata->has_dualshock_touchpad) {
+ if (device->has_dualshock_touchpad) {
GCControllerDirectionPad *dpad;
buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputDualShockTouchpadButton].isPressed;
@@ -1056,17 +1105,17 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
}
- if (joystick->hwdata->has_xbox_paddles) {
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1)) {
+ if (device->has_xbox_paddles) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE1)) {
buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleOne].isPressed;
}
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE1)) {
buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleTwo].isPressed;
}
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_RIGHT_PADDLE2)) {
buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleThree].isPressed;
}
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2)) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_LEFT_PADDLE2)) {
buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxPaddleFour].isPressed;
}
@@ -1079,7 +1128,7 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
*/
}
- if (joystick->hwdata->has_xbox_share_button) {
+ if (device->has_xbox_share_button) {
buttons[button_count++] = controller.physicalInputProfile.buttons[GCInputXboxShareButton].isPressed;
}
#endif
@@ -1170,8 +1219,8 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability-new"
/* This must be the last button, so we can optionally handle it with pause_button_index below */
- if (joystick->hwdata->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
- if (joystick->hwdata->uses_pause_handler) {
+ if (device->button_mask & (1 << SDL_GAMEPAD_BUTTON_START)) {
+ if (device->uses_pause_handler) {
pause_button_index = button_count;
buttons[button_count++] = joystick->delayed_guide_button;
} else {
@@ -1186,16 +1235,16 @@ static void IOS_MFIJoystickUpdate(SDL_Joystick *joystick)
}
#endif /* TARGET_OS_TV */
- if (joystick->nhats > 0) {
+ if (joystick->nhats > 0 && !device->use_physical_profile) {
SDL_SendJoystickHat(timestamp, joystick, 0, hatstate);
}
- if (joystick->hwdata->uses_pause_handler) {
- for (i = 0; i < joystick->hwdata->num_pause_presses; i++) {
+ if (device->uses_pause_handler) {
+ for (i = 0; i < device->num_pause_presses; i++) {
SDL_SendJoystickButton(timestamp, joystick, pause_button_index, SDL_PRESSED);
SDL_SendJoystickButton(timestamp, joystick, pause_button_index, SDL_RELEASED);
}
- joystick->hwdata->num_pause_presses = 0;
+ device->num_pause_presses = 0;
}
#ifdef ENABLE_MFI_BATTERY
@@ -1732,6 +1781,10 @@ static void GetAppleSFSymbolsNameForElement(GCControllerElement *element, char *
return controller.microGamepad.dpad;
}
+ if ([controller respondsToSelector:@selector(physicalInputProfile)]) {
+ return controller.physicalInputProfile.dpads[GCInputDirectionPad];
+ }
+
return nil;
}
#endif /* SDL_JOYSTICK_MFI && ENABLE_PHYSICAL_INPUT_PROFILE */
diff --git a/src/joystick/apple/SDL_mfijoystick_c.h b/src/joystick/apple/SDL_mfijoystick_c.h
index 553a6307c8d8..06a9cd8f2b96 100644
--- a/src/joystick/apple/SDL_mfijoystick_c.h
+++ b/src/joystick/apple/SDL_mfijoystick_c.h
@@ -25,6 +25,8 @@
#include "../SDL_sysjoystick.h"
+#include <CoreFoundation/CoreFoundation.h>
+
@class GCController;
typedef struct joystick_hwdata
@@ -51,6 +53,11 @@ typedef struct joystick_hwdata
SDL_bool has_xbox_paddles;
SDL_bool has_xbox_share_button;
+ SDL_bool use_physical_profile;
+ NSArray *axes;
+ NSArray *dpads;
+ NSArray *buttons;
+
struct joystick_hwdata *next;
} joystick_hwdata;