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 <smcv@collabora.com> (cherry picked from commit cf1dc66e2cfc7a65374c5fea681dd31c50363a2c)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
diff --git a/src/joystick/linux/SDL_sysjoystick.c b/src/joystick/linux/SDL_sysjoystick.c
index 8acef20..6d1c90d 100644
--- a/src/joystick/linux/SDL_sysjoystick.c
+++ b/src/joystick/linux/SDL_sysjoystick.c
@@ -1671,6 +1671,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)
{
@@ -1891,14 +1893,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;
@@ -1908,13 +1931,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;
@@ -2035,7 +2068,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];
@@ -2044,6 +2086,16 @@ static SDL_bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMap
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
}
LINUX_JoystickClose(joystick);