Repository: godotengine/godot (language: cpp)

Issue:
ABXY button mapping incorrect for Nintendo Switch Pro Controller
### Tested versions

Reproducible in Godot v4.3.dev (b15105a35)

### System information

Godot v4.3.dev (b15105a35) - macOS 14.3.1 - Vulkan (Forward+) - dedicated AMD Radeon Pro 5500 XT - Intel(R) Core(TM) i7-10700K CPU @ 3.80GHz (16 Threads)

### Issue description

The mappings for the A, B, X, and Y buttons on the Nintendo Switch Pro Controller are off. The A and B buttons are switched, as are the X and Y buttons. I've attached a short video of my pressing the buttons on the controller and seeing what the editor picked up.

https://github.com/godotengine/godot/assets/9957987/0598d279-d0de-462b-8e24-4bc9ceb4c182


### Steps to reproduce

1. Use a very recent version of Godot (presumably one since #80709 was merged).
2. In a fresh project, go to Project Settings > Input Map, add a new action, and open the Event Configuration window for that action.
3. Connect a Nintendo Switch Pro Controller to the computer.
4. Press the A, B, X, and Y buttons on the controller, and see what button Godot registers.
    - If you press A, Godot will register `Joypad Button 0 (Bottom Action, Sony Cross, Xbox A, Nintendo B)`, despite it being the Right Action button.
    - If you press B, Godot should register `Joypad Button 1 (Right Action, Sony Circle, Xbox B, Nintendo A)`, despite it being the Bottom Action button.
    - If you press X, Godot should register `Joypad Button 2 (Left Action, Sony Square, Xbox X, Nintendo Y)`, despite it being the Top Action button.
    - If you press Y, Godot should register `Joypad Button 3 (Top Action, Sony Triangle, Xbox Y, Nintendo X)`, despite it being the Left Action button.

### Minimal reproduction project (MRP)

N/A


Discussion / hints:
I get similar mapping errors with an Xbox One controller on Mac, using a similar system and Godot 4.3.dev setup, as in #68976
> I get similar mapping errors with an Xbox One controller on Mac, using a similar system and Godot 4.3.dev setup, as in #68976

Which 4.3.dev commit are you using? https://github.com/godotengine/godot/pull/80709 was merged after 4.3.dev3 was released, so I suggest building `master` from source and testing this again.
I tested my Xbox Wireless Controller (model 1708) on 4.3.dev3, and its inputs appear to be working as expected:

https://github.com/godotengine/godot/assets/9957987/b5df244d-3f0e-4c0d-abd5-993bcfa3da6e

On v4.3.dev.gh [b15105a35], the Xbox Wireless Controller's ABXY buttons all seem to be mapped correctly for me as well. *However*, Godot no longer recognizes controller inputs when it is not focused:

https://github.com/godotengine/godot/assets/9957987/3f72ac01-6196-4778-955a-6d3663d67a9b

I'm not sure if this is something I should open a new issue for...? 😅 
> However, Godot no longer recognizes controller inputs when it is not focused:

This may be a design decision in the GameController API. cc @bruvzg 

However, many people may actually welcome this new behavior, even though it's inconsistent with other platforms: https://github.com/godotengine/godot/issues/16832
It should be possible to enable background listening (it's off by default on macOS 11.3+).
I was able to clone, build, and run the Godot source today for the first time! 😁 I *might* see spots in the code that could be responsible for the button mapping issue, so I'll see if I can fix it.
> > I get similar mapping errors with an Xbox One controller on Mac, using a similar system and Godot 4.3.dev setup, as in #68976
> 
> Which 4.3.dev commit are you using? #80709 was merged after 4.3.dev3 was released, so I suggest building `master` from source and testing this again.

You are correct, this version fixes the xbox one controller mapping issue for me. Thanks!
Okay, so upon a little further investigation, I have good news and bad news.

The good news: this doesn't appear to be a bug in Godot or #80709 !

The bad news: it's just how Apple decided the Pro Controller should be mapped. 🙃 
In this video, I'm pressing A, B, Y, X in order. But the controller test website displays the same wrong mapping, until I go into the Game Controllers section of System Settings and manually remap the buttons.

 
https://github.com/godotengine/godot/assets/9957987/2cf0181a-8322-485b-bc16-fab6faec72d9

I assume Apple did this with the intent of preserving the "semantic meaning" of the buttons - the right button is "confirm" everywhere, the bottom button is "cancel" everywhere, etc. But it's quite frustrating that this no longer has a consistent physical mapping across controllers. How should the Godot side proceed with this?

If anyone else happens to be familiar with the GameController API and knows a good solution to this, then that would be great! Otherwise, over the coming days, I may see what I can learn about it and if there's a way to query the controller for the hardware-level buttons instead of going through the inputs after they've been processed by the API and profiles. Don't know if that'll lead anywhere, but I suppose it's worth a shot.
SDL have remapping for these buttons hardcoded (based on controller name), we probably should do the same:

https://github.com/libsdl-org/SDL/blob/0c6a1b636e42b7193027383d75f509a9e5d00214/src/joystick/apple/SDL_mfijoystick.m#L1786-L1806
https://github.com/godotengine/godot/pull/89001

Fix patch:
diff --git a/drivers/apple/joypad_apple.h b/drivers/apple/joypad_apple.h
index 909648e253dd..d89707c03d75 100644
--- a/drivers/apple/joypad_apple.h
+++ b/drivers/apple/joypad_apple.h
@@ -43,6 +43,7 @@ struct GameController {
 	RumbleContext *rumble_context API_AVAILABLE(macos(11.0), ios(14.0), tvos(14.0)) = nil;
 	NSInteger ff_effect_timestamp = 0;
 	bool force_feedback = false;
+	bool nintendo_button_layout = false;
 
 	GameController(int p_joy_id, GCController *p_controller);
 	~GameController();
diff --git a/drivers/apple/joypad_apple.mm b/drivers/apple/joypad_apple.mm
index 148ec4cb2718..e14fc96a2841 100644
--- a/drivers/apple/joypad_apple.mm
+++ b/drivers/apple/joypad_apple.mm
@@ -144,6 +144,12 @@ void play_strong_pattern(CHHapticPattern *p_pattern) {
 		}
 	}
 
+	if (@available(macOS 10.15, iOS 13.0, tvOS 13.0, *)) {
+		if ([controller.productCategory isEqualToString:@"Switch Pro Controller"] || [controller.productCategory isEqualToString:@"Nintendo Switch Joy-Con (L/R)"]) {
+			nintendo_button_layout = true;
+		}
+	}
+
 	int l_joy_id = joy_id;
 
 	auto BUTTON = [l_joy_id](JoyButton p_button) {
@@ -155,10 +161,18 @@ void play_strong_pattern(CHHapticPattern *p_pattern) {
 	if (controller.extendedGamepad != nil) {
 		GCExtendedGamepad *gamepad = controller.extendedGamepad;
 
-		gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
-		gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::B);
-		gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
-		gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
+		if (nintendo_button_layout) {
+			gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::B);
+			gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::A);
+			gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::Y);
+			gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::X);
+		} else {
+			gamepad.buttonA.pressedChangedHandler = BUTTON(JoyButton::A);
+			gamepad.buttonB.pressedChangedHandler = BUTTON(JoyButton::B);
+			gamepad.buttonX.pressedChangedHandler = BUTTON(JoyButton::X);
+			gamepad.buttonY.pressedChangedHandler = BUTTON(JoyButton::Y);
+		}
+
 		gamepad.leftShoulder.pressedChangedHandler = BUTTON(JoyButton::LEFT_SHOULDER);
 		gamepad.rightShoulder.pressedChangedHandler = BUTTON(JoyButton::RIGHT_SHOULDER);
 		gamepad.dpad.up.pressedChangedHandler = BUTTON(JoyButton::DPAD_UP);


Test patch: