Skip to content

Commit 249535a

Browse files
committed
joystick: Improve Xbox controller mapping with xpad quirks
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 249535a

File tree

3 files changed

+132
-28
lines changed

3 files changed

+132
-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: 114 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,9 @@ static void FreeJoylistItem(SDL_joylist_item *item)
422444
SDL_free(item->mapping);
423445
SDL_free(item->path);
424446
SDL_free(item->name);
447+
if (item->driver) {
448+
SDL_free(item->driver);
449+
}
425450
SDL_free(item);
426451
}
427452

@@ -436,6 +461,7 @@ static void MaybeAddDevice(const char *path)
436461
struct stat sb;
437462
int fd = -1;
438463
char *name = NULL;
464+
char *driver = NULL;
439465
Uint16 vendor, product;
440466
SDL_GUID guid;
441467
SDL_joylist_item *item;
@@ -473,7 +499,7 @@ static void MaybeAddDevice(const char *path)
473499
SDL_Log("Checking %s", path);
474500
#endif
475501

476-
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) {
502+
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) {
477503
#ifdef DEBUG_INPUT_EVENTS
478504
SDL_Log("found joystick: %s", path);
479505
#endif
@@ -488,6 +514,7 @@ static void MaybeAddDevice(const char *path)
488514
item->path = SDL_strdup(path);
489515
item->name = name;
490516
item->guid = guid;
517+
item->driver = driver;
491518

492519
if (vendor == USB_VENDOR_VALVE &&
493520
product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
@@ -861,7 +888,7 @@ static void LINUX_ScanSteamVirtualGamepads(void)
861888
// Opening input devices can generate synchronous device I/O, so avoid it if we can
862889
class = 0;
863890
SDL_zero(inpid);
864-
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
891+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
865892
(inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
866893
free(entries[i]); // This should NOT be SDL_free()
867894
continue;
@@ -2244,6 +2271,12 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
22442271
MAPPED_DPAD_LEFT = 0x4,
22452272
MAPPED_DPAD_RIGHT = 0x8,
22462273
MAPPED_DPAD_ALL = 0xF,
2274+
2275+
MAPPED_LEFT_PADDLE1 = 0x1,
2276+
MAPPED_RIGHT_PADDLE1 = 0x2,
2277+
MAPPED_LEFT_PADDLE2 = 0x4,
2278+
MAPPED_RIGHT_PADDLE2 = 0x8,
2279+
MAPPED_PADDLE_ALL = 0xF,
22472280
};
22482281
unsigned int mapped;
22492282
bool result = false;
@@ -2609,6 +2642,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26092642
SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target);
26102643
SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target);
26112644
#endif
2645+
} else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
2646+
// xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
2647+
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
2648+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
2649+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
2650+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
2651+
out->dpleft.kind = EMappingKind_Button;
2652+
out->dpright.kind = EMappingKind_Button;
2653+
out->dpup.kind = EMappingKind_Button;
2654+
out->dpdown.kind = EMappingKind_Button;
2655+
out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
2656+
out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
2657+
out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
2658+
out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
2659+
#ifdef DEBUG_GAMEPAD_MAPPING
2660+
SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target);
2661+
SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target);
2662+
SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target);
2663+
SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target);
2664+
#endif
2665+
}
26122666
}
26132667
}
26142668

@@ -2653,8 +2707,44 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26532707
#endif
26542708
}
26552709

2656-
if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
2710+
mapped = 0;
2711+
2712+
if (joystick->hwdata->has_key[BTN_GRIPR]) {
2713+
out->right_paddle1.kind = EMappingKind_Button;
2714+
out->right_paddle1.target = joystick->hwdata->key_map[BTN_GRIPR];
2715+
mapped |= MAPPED_RIGHT_PADDLE1;
2716+
#ifdef DEBUG_GAMEPAD_MAPPING
2717+
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_GRIPR)", out->right_paddle1.target);
2718+
#endif
2719+
}
2720+
if (joystick->hwdata->has_key[BTN_GRIPL]) {
2721+
out->left_paddle1.kind = EMappingKind_Button;
2722+
out->left_paddle1.target = joystick->hwdata->key_map[BTN_GRIPL];
2723+
mapped |= MAPPED_LEFT_PADDLE1;
2724+
#ifdef DEBUG_GAMEPAD_MAPPING
2725+
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_GRIPL)", out->left_paddle1.target);
2726+
#endif
2727+
}
2728+
if (joystick->hwdata->has_key[BTN_GRIPR2]) {
2729+
out->right_paddle2.kind = EMappingKind_Button;
2730+
out->right_paddle2.target = joystick->hwdata->key_map[BTN_GRIPR2];
2731+
mapped |= MAPPED_RIGHT_PADDLE2;
2732+
#ifdef DEBUG_GAMEPAD_MAPPING
2733+
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_GRIPR)", out->right_paddle2.target);
2734+
#endif
2735+
}
2736+
if (joystick->hwdata->has_key[BTN_GRIPL2]) {
2737+
out->left_paddle2.kind = EMappingKind_Button;
2738+
out->left_paddle2.target = joystick->hwdata->key_map[BTN_GRIPL2];
2739+
mapped |= MAPPED_LEFT_PADDLE2;
2740+
#ifdef DEBUG_GAMEPAD_MAPPING
2741+
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_GRIPL2)", out->left_paddle2.target);
2742+
#endif
2743+
}
2744+
2745+
if (mapped != MAPPED_PADDLE_ALL && SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
26572746
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8
2747+
// in older drivers
26582748
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] &&
26592749
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] &&
26602750
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY7] &&
@@ -2667,22 +2757,23 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26672757
out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY6];
26682758
out->left_paddle2.kind = EMappingKind_Button;
26692759
out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY8];
2760+
mapped = MAPPED_PADDLE_ALL;
26702761
#ifdef DEBUG_GAMEPAD_MAPPING
26712762
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY5)", out->right_paddle1.target);
26722763
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY7)", out->left_paddle1.target);
26732764
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY6)", out->right_paddle2.target);
26742765
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target);
26752766
#endif
26762767
}
2768+
}
26772769

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];
2770+
// Xbox Series controllers have the Share button as KEY_RECORD
2771+
if (joystick->hwdata->has_key[KEY_RECORD]) {
2772+
out->misc1.kind = EMappingKind_Button;
2773+
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
26822774
#ifdef DEBUG_GAMEPAD_MAPPING
2683-
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
2775+
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
26842776
#endif
2685-
}
26862777
}
26872778

26882779
// Cache the mapping for later

0 commit comments

Comments
 (0)