Skip to content

Commit 5f4416b

Browse files
authored
joystick: Improve Xbox controller mapping with xpad quirks (#13305)
xpad has a series of questionable design choices when it comes to button mapping. Notably, BTN_TRIGGER_HAPPY1-4 are used for the D-pad on dance mats instead of the typical BTN_DPAD_*, and maps the paddles to BTN_TRIGGER_HAPPY5-8. This commit plumbs through driver detection for a udev device and adds special exceptions for xpad's quirks. Newer kernels (6.17+) add the BTN_GRIP* mappings for paddles on controllers. We should prefer that if available, as its meaning and mapping is unambiguous. However, since it's only in new kernels, we need to maintain the older mappings where we know they exist. It also exposes KEY_RECORD as the share button regardless of vendor, which had previously been gated behind being a Microsoft controller. All Xbox Series controllers can include this button, and many third party ones do.
1 parent 25cf1a5 commit 5f4416b

File tree

3 files changed

+130
-28
lines changed

3 files changed

+130
-28
lines changed

src/core/linux/SDL_udev.c

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ static bool SDL_UDEV_load_syms(void)
6868

6969
SDL_UDEV_SYM(udev_device_get_action);
7070
SDL_UDEV_SYM(udev_device_get_devnode);
71+
SDL_UDEV_SYM(udev_device_get_driver);
7172
SDL_UDEV_SYM(udev_device_get_syspath);
7273
SDL_UDEV_SYM(udev_device_get_subsystem);
7374
SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
@@ -219,7 +220,7 @@ bool SDL_UDEV_Scan(void)
219220
return true;
220221
}
221222

222-
bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
223+
bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver)
223224
{
224225
struct stat statbuf;
225226
char type;
@@ -253,17 +254,27 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr
253254

254255
val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
255256
if (val) {
256-
*vendor = (Uint16)SDL_strtol(val, NULL, 16);
257+
inpid->vendor = (Uint16)SDL_strtol(val, NULL, 16);
257258
}
258259

259260
val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
260261
if (val) {
261-
*product = (Uint16)SDL_strtol(val, NULL, 16);
262+
inpid->product = (Uint16)SDL_strtol(val, NULL, 16);
262263
}
263264

264265
val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
265266
if (val) {
266-
*version = (Uint16)SDL_strtol(val, NULL, 16);
267+
inpid->version = (Uint16)SDL_strtol(val, NULL, 16);
268+
}
269+
270+
if (driver) {
271+
val = _this->syms.udev_device_get_driver(dev);
272+
if (!val) {
273+
val = _this->syms.udev_device_get_property_value(dev, "ID_USB_DRIVER");
274+
}
275+
if (val) {
276+
*driver = SDL_strdup(val);
277+
}
267278
}
268279

269280
class_temp = device_class(dev);

src/core/linux/SDL_udev.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#endif
3232

3333
#include <libudev.h>
34+
#include <linux/input.h>
3435
#include <sys/time.h>
3536
#include <sys/types.h>
3637

@@ -56,6 +57,7 @@ typedef struct SDL_UDEV_Symbols
5657
{
5758
const char *(*udev_device_get_action)(struct udev_device *);
5859
const char *(*udev_device_get_devnode)(struct udev_device *);
60+
const char *(*udev_device_get_driver)(struct udev_device *);
5961
const char *(*udev_device_get_syspath)(struct udev_device *);
6062
const char *(*udev_device_get_subsystem)(struct udev_device *);
6163
struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
@@ -102,7 +104,7 @@ extern void SDL_UDEV_UnloadLibrary(void);
102104
extern bool SDL_UDEV_LoadLibrary(void);
103105
extern void SDL_UDEV_Poll(void);
104106
extern bool SDL_UDEV_Scan(void);
105-
extern bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class);
107+
extern bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver);
106108
extern bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb);
107109
extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb);
108110
extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void);

src/joystick/linux/SDL_sysjoystick.c

Lines changed: 112 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@
7575
#ifndef BTN_DPAD_RIGHT
7676
#define BTN_DPAD_RIGHT 0x223
7777
#endif
78+
#ifndef BTN_GRIPL
79+
#define BTN_GRIPL 0x224
80+
#endif
81+
#ifndef BTN_GRIPR
82+
#define BTN_GRIPR 0x225
83+
#endif
84+
#ifndef BTN_GRIPL2
85+
#define BTN_GRIPL2 0x226
86+
#endif
87+
#ifndef BTN_GRIPR2
88+
#define BTN_GRIPR2 0x227
89+
#endif
7890

7991
#ifndef BTN_TRIGGER_HAPPY
8092
#define BTN_TRIGGER_HAPPY 0x2c0
@@ -151,6 +163,7 @@ typedef struct SDL_joylist_item
151163
SDL_JoystickID device_instance;
152164
char *path; // "/dev/input/event2" or whatever
153165
char *name; // "SideWinder 3D Pro" or whatever
166+
char *driver; // "xpad" or whatever
154167
SDL_GUID guid;
155168
dev_t devnum;
156169
int steam_virtual_gamepad_slot;
@@ -274,54 +287,54 @@ static bool GuessIsSensor(int fd)
274287
return false;
275288
}
276289

277-
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid)
290+
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid, char **driver_return)
278291
{
279292
struct input_id inpid;
280-
char *name;
293+
char *name = NULL;
294+
char *driver = NULL;
281295
char product_string[128];
282296
int class = 0;
283297

284298
SDL_zero(inpid);
285299
#ifdef SDL_USE_LIBUDEV
286300
// Opening input devices can generate synchronous device I/O, so avoid it if we can
287-
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
301+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, &driver) &&
288302
!(class & SDL_UDEV_DEVICE_JOYSTICK)) {
289-
return false;
303+
goto error;
290304
}
291305
#endif
292306

293307
if (fd && *fd < 0) {
294308
*fd = open(path, O_RDONLY | O_CLOEXEC, 0);
295309
}
296310
if (!fd || *fd < 0) {
297-
return false;
311+
goto error;
298312
}
299313

300314
if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) {
301315
// When udev enumeration or classification, we only got joysticks here, so no need to test
302316
if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) {
303-
return false;
317+
goto error;
304318
}
305319

306320
// Could have vendor and product already from udev, but should agree with evdev
307321
if (ioctl(*fd, EVIOCGID, &inpid) < 0) {
308-
return false;
322+
goto error;
309323
}
310324

311325
if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) {
312-
return false;
326+
goto error;
313327
}
314328
}
315329

316330
name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string);
317331
if (!name) {
318-
return false;
332+
goto error;
319333
}
320334

321335
if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) &&
322336
SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) {
323-
SDL_free(name);
324-
return false;
337+
goto error;
325338
}
326339

327340
FixupDeviceInfoForMapping(*fd, &inpid);
@@ -331,14 +344,23 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve
331344
#endif
332345

333346
if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) {
334-
SDL_free(name);
335-
return false;
347+
goto error;
336348
}
337349
*name_return = name;
350+
*driver_return = driver;
338351
*vendor_return = inpid.vendor;
339352
*product_return = inpid.product;
340353
*guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0);
341354
return true;
355+
356+
error:
357+
if (driver) {
358+
SDL_free(driver);
359+
}
360+
if (name) {
361+
SDL_free(name);
362+
}
363+
return false;
342364
}
343365

344366
static bool IsSensor(const char *path, int *fd)
@@ -349,7 +371,7 @@ static bool IsSensor(const char *path, int *fd)
349371
SDL_zero(inpid);
350372
#ifdef SDL_USE_LIBUDEV
351373
// Opening input devices can generate synchronous device I/O, so avoid it if we can
352-
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
374+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
353375
!(class & SDL_UDEV_DEVICE_ACCELEROMETER)) {
354376
return false;
355377
}
@@ -422,6 +444,7 @@ static void FreeJoylistItem(SDL_joylist_item *item)
422444
SDL_free(item->mapping);
423445
SDL_free(item->path);
424446
SDL_free(item->name);
447+
SDL_free(item->driver);
425448
SDL_free(item);
426449
}
427450

@@ -436,6 +459,7 @@ static void MaybeAddDevice(const char *path)
436459
struct stat sb;
437460
int fd = -1;
438461
char *name = NULL;
462+
char *driver = NULL;
439463
Uint16 vendor, product;
440464
SDL_GUID guid;
441465
SDL_joylist_item *item;
@@ -473,7 +497,7 @@ static void MaybeAddDevice(const char *path)
473497
SDL_Log("Checking %s", path);
474498
#endif
475499

476-
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) {
500+
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) {
477501
#ifdef DEBUG_INPUT_EVENTS
478502
SDL_Log("found joystick: %s", path);
479503
#endif
@@ -488,6 +512,7 @@ static void MaybeAddDevice(const char *path)
488512
item->path = SDL_strdup(path);
489513
item->name = name;
490514
item->guid = guid;
515+
item->driver = driver;
491516

492517
if (vendor == USB_VENDOR_VALVE &&
493518
product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
@@ -861,7 +886,7 @@ static void LINUX_ScanSteamVirtualGamepads(void)
861886
// Opening input devices can generate synchronous device I/O, so avoid it if we can
862887
class = 0;
863888
SDL_zero(inpid);
864-
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
889+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
865890
(inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
866891
free(entries[i]); // This should NOT be SDL_free()
867892
continue;
@@ -2244,6 +2269,12 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
22442269
MAPPED_DPAD_LEFT = 0x4,
22452270
MAPPED_DPAD_RIGHT = 0x8,
22462271
MAPPED_DPAD_ALL = 0xF,
2272+
2273+
MAPPED_LEFT_PADDLE1 = 0x1,
2274+
MAPPED_RIGHT_PADDLE1 = 0x2,
2275+
MAPPED_LEFT_PADDLE2 = 0x4,
2276+
MAPPED_RIGHT_PADDLE2 = 0x8,
2277+
MAPPED_PADDLE_ALL = 0xF,
22472278
};
22482279
unsigned int mapped;
22492280
bool result = false;
@@ -2609,6 +2640,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26092640
SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target);
26102641
SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target);
26112642
#endif
2643+
} else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
2644+
// xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
2645+
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
2646+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
2647+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
2648+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
2649+
out->dpleft.kind = EMappingKind_Button;
2650+
out->dpright.kind = EMappingKind_Button;
2651+
out->dpup.kind = EMappingKind_Button;
2652+
out->dpdown.kind = EMappingKind_Button;
2653+
out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
2654+
out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
2655+
out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
2656+
out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
2657+
#ifdef DEBUG_GAMEPAD_MAPPING
2658+
SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target);
2659+
SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target);
2660+
SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target);
2661+
SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target);
2662+
#endif
2663+
}
26122664
}
26132665
}
26142666

@@ -2653,8 +2705,44 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26532705
#endif
26542706
}
26552707

2656-
if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
2708+
mapped = 0;
2709+
2710+
if (joystick->hwdata->has_key[BTN_GRIPR]) {
2711+
out->right_paddle1.kind = EMappingKind_Button;
2712+
out->right_paddle1.target = joystick->hwdata->key_map[BTN_GRIPR];
2713+
mapped |= MAPPED_RIGHT_PADDLE1;
2714+
#ifdef DEBUG_GAMEPAD_MAPPING
2715+
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_GRIPR)", out->right_paddle1.target);
2716+
#endif
2717+
}
2718+
if (joystick->hwdata->has_key[BTN_GRIPL]) {
2719+
out->left_paddle1.kind = EMappingKind_Button;
2720+
out->left_paddle1.target = joystick->hwdata->key_map[BTN_GRIPL];
2721+
mapped |= MAPPED_LEFT_PADDLE1;
2722+
#ifdef DEBUG_GAMEPAD_MAPPING
2723+
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_GRIPL)", out->left_paddle1.target);
2724+
#endif
2725+
}
2726+
if (joystick->hwdata->has_key[BTN_GRIPR2]) {
2727+
out->right_paddle2.kind = EMappingKind_Button;
2728+
out->right_paddle2.target = joystick->hwdata->key_map[BTN_GRIPR2];
2729+
mapped |= MAPPED_RIGHT_PADDLE2;
2730+
#ifdef DEBUG_GAMEPAD_MAPPING
2731+
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_GRIPR)", out->right_paddle2.target);
2732+
#endif
2733+
}
2734+
if (joystick->hwdata->has_key[BTN_GRIPL2]) {
2735+
out->left_paddle2.kind = EMappingKind_Button;
2736+
out->left_paddle2.target = joystick->hwdata->key_map[BTN_GRIPL2];
2737+
mapped |= MAPPED_LEFT_PADDLE2;
2738+
#ifdef DEBUG_GAMEPAD_MAPPING
2739+
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_GRIPL2)", out->left_paddle2.target);
2740+
#endif
2741+
}
2742+
2743+
if (mapped != MAPPED_PADDLE_ALL && SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
26572744
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8
2745+
// in older drivers
26582746
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] &&
26592747
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] &&
26602748
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] &&
@@ -2667,22 +2755,23 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26672755
out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6];
26682756
out->left_paddle2.kind = EMappingKind_Button;
26692757
out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8];
2758+
mapped = MAPPED_PADDLE_ALL;
26702759
#ifdef DEBUG_GAMEPAD_MAPPING
26712760
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target);
26722761
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target);
26732762
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY6)", out->right_paddle2.target);
26742763
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target);
26752764
#endif
26762765
}
2766+
}
26772767

2678-
// The Xbox Series X controllers have the Share button as KEY_RECORD
2679-
if (joystick->hwdata->has_key[KEY_RECORD]) {
2680-
out->misc1.kind = EMappingKind_Button;
2681-
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
2768+
// Xbox Series controllers have the Share button as KEY_RECORD
2769+
if (joystick->hwdata->has_key[KEY_RECORD]) {
2770+
out->misc1.kind = EMappingKind_Button;
2771+
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
26822772
#ifdef DEBUG_GAMEPAD_MAPPING
2683-
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
2773+
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
26842774
#endif
2685-
}
26862775
}
26872776

26882777
// Cache the mapping for later

0 commit comments

Comments
 (0)