From 5804fcd218bb4d854368c6eb1674d8acaa1a59e4 Mon Sep 17 00:00:00 2001 From: Simon McVittie Date: Fri, 9 Jun 2023 13:24:21 +0100 Subject: [PATCH] linux: Improve gamepad mapping heuristic to accept Android conventions This heuristic for gamepads without a more specific mapping already tried two incompatible conventions for handling triggers: the Linux Gamepad Specification uses hat switch 2 for the triggers (for whatever reason), but the de facto standard set by the drivers for older Xbox and Playstation controllers represents each trigger as the Z-axis of the nearest analog stick. Android documentation encourages Bluetooth gamepad manufacturers to use a third incompatible convention where the left and right triggers are represented as the brake and gas pedals of a driving simulator controller. The Android convention also changes the representation of the right stick: instead of using X and Y rotation as a second pair of axes, Android uses Z position as a second horizontal axis, and Z rotation as a second vertical axis. Try to cope gracefully with all of these. This will hopefully resolve the issue described in #5406 (when using unpatched kernels). Signed-off-by: Simon McVittie --- src/joystick/linux/SDL_sysjoystick.c | 52 ++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c index f175d8ad93c7d..a2e20ce4f1f5a 100644 --- a/src/joystick/linux/SDL_sysjoystick.c +++ b/src/joystick/linux/SDL_sysjoystick.c @@ -1608,6 +1608,8 @@ static void LINUX_JoystickQuit(void) /* This is based on the Linux Gamepad Specification available at: https://www.kernel.org/doc/html/v4.15/input/gamepad.html + and the Android gamepad documentation, + https://developer.android.com/develop/ui/views/touch-and-input/game-controllers/controller-input */ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping *out) { @@ -1828,14 +1830,35 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap /* Prefer analog triggers, but settle for digital hat or buttons. */ mapped = 0; + /* Unfortunately there are several conventions for how analog triggers + * are represented as absolute axes: + * + * - Linux Gamepad Specification: + * LT = ABS_HAT2Y, RT = ABS_HAT2X + * - Android (and therefore many Bluetooth controllers): + * LT = ABS_BRAKE, RT = ABS_GAS + * - De facto standard for older Xbox and Playstation controllers: + * LT = ABS_Z, RT = ABS_RZ + * + * We try each one in turn. */ if (joystick->hwdata->has_abs[ABS_HAT2Y]) { + /* Linux Gamepad Specification */ out->lefttrigger.kind = EMappingKind_Axis; out->lefttrigger.target = joystick->hwdata->abs_map[ABS_HAT2Y]; mapped |= MAPPED_TRIGGER_LEFT; #ifdef DEBUG_GAMEPAD_MAPPING SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_HAT2Y)", out->lefttrigger.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_BRAKE]) { + /* Android convention */ + out->lefttrigger.kind = EMappingKind_Axis; + out->lefttrigger.target = joystick->hwdata->abs_map[ABS_BRAKE]; + mapped |= MAPPED_TRIGGER_LEFT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped LEFTTRIGGER to axis %d (ABS_BRAKE)", out->lefttrigger.target); #endif } else if (joystick->hwdata->has_abs[ABS_Z]) { + /* De facto standard for Xbox 360 and Playstation gamepads */ out->lefttrigger.kind = EMappingKind_Axis; out->lefttrigger.target = joystick->hwdata->abs_map[ABS_Z]; mapped |= MAPPED_TRIGGER_LEFT; @@ -1845,13 +1868,23 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap } if (joystick->hwdata->has_abs[ABS_HAT2X]) { + /* Linux Gamepad Specification */ out->righttrigger.kind = EMappingKind_Axis; out->righttrigger.target = joystick->hwdata->abs_map[ABS_HAT2X]; mapped |= MAPPED_TRIGGER_RIGHT; #ifdef DEBUG_GAMEPAD_MAPPING SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_HAT2X)", out->righttrigger.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_GAS]) { + /* Android convention */ + out->righttrigger.kind = EMappingKind_Axis; + out->righttrigger.target = joystick->hwdata->abs_map[ABS_GAS]; + mapped |= MAPPED_TRIGGER_RIGHT; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTTRIGGER to axis %d (ABS_GAS)", out->righttrigger.target); #endif } else if (joystick->hwdata->has_abs[ABS_RZ]) { + /* De facto standard for Xbox 360 and Playstation gamepads */ out->righttrigger.kind = EMappingKind_Axis; out->righttrigger.target = joystick->hwdata->abs_map[ABS_RZ]; mapped |= MAPPED_TRIGGER_RIGHT; @@ -1972,7 +2005,16 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap #endif } + /* The Linux Gamepad Specification uses the RX and RY axes, + * originally intended to represent X and Y rotation, as a second + * joystick. This is common for USB gamepads, and also many Bluetooth + * gamepads, particularly older ones. + * + * The Android mapping convention used by many Bluetooth controllers + * instead uses the Z axis as a secondary X axis, and the RZ axis as + * a secondary Y axis. */ if (joystick->hwdata->has_abs[ABS_RX] && joystick->hwdata->has_abs[ABS_RY]) { + /* Linux Gamepad Specification, Xbox 360, Playstation etc. */ out->rightx.kind = EMappingKind_Axis; out->righty.kind = EMappingKind_Axis; out->rightx.target = joystick->hwdata->abs_map[ABS_RX]; @@ -1980,6 +2022,16 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap #ifdef DEBUG_GAMEPAD_MAPPING SDL_Log("Mapped RIGHTX to axis %d (ABS_RX)", out->rightx.target); SDL_Log("Mapped RIGHTY to axis %d (ABS_RY)", out->righty.target); +#endif + } else if (joystick->hwdata->has_abs[ABS_Z] && joystick->hwdata->has_abs[ABS_RZ]) { + /* Android convention */ + out->rightx.kind = EMappingKind_Axis; + out->righty.kind = EMappingKind_Axis; + out->rightx.target = joystick->hwdata->abs_map[ABS_Z]; + out->righty.target = joystick->hwdata->abs_map[ABS_RZ]; +#ifdef DEBUG_GAMEPAD_MAPPING + SDL_Log("Mapped RIGHTX to axis %d (ABS_Z)", out->rightx.target); + SDL_Log("Mapped RIGHTY to axis %d (ABS_RZ)", out->righty.target); #endif }