Skip to content

Commit 4fdbd34

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. When not using xpad, it assumes that the paddles are instead mapped to BTN_TRIGGER_HAPPY1-4, which is what upcoming driver projects use instead. 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 89eef1b commit 4fdbd34

File tree

3 files changed

+98
-28
lines changed

3 files changed

+98
-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: 80 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ typedef struct SDL_joylist_item
151151
SDL_JoystickID device_instance;
152152
char *path; // "/dev/input/event2" or whatever
153153
char *name; // "SideWinder 3D Pro" or whatever
154+
char *driver; // "xpad" or whatever
154155
SDL_GUID guid;
155156
dev_t devnum;
156157
int steam_virtual_gamepad_slot;
@@ -274,54 +275,54 @@ static bool GuessIsSensor(int fd)
274275
return false;
275276
}
276277

277-
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid)
278+
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid, char **driver_return)
278279
{
279280
struct input_id inpid;
280-
char *name;
281+
char *name = NULL;
282+
char *driver = NULL;
281283
char product_string[128];
282284
int class = 0;
283285

284286
SDL_zero(inpid);
285287
#ifdef SDL_USE_LIBUDEV
286288
// 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) &&
289+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, &driver) &&
288290
!(class & SDL_UDEV_DEVICE_JOYSTICK)) {
289-
return false;
291+
goto error;
290292
}
291293
#endif
292294

293295
if (fd && *fd < 0) {
294296
*fd = open(path, O_RDONLY | O_CLOEXEC, 0);
295297
}
296298
if (!fd || *fd < 0) {
297-
return false;
299+
goto error;
298300
}
299301

300302
if (ioctl(*fd, JSIOCGNAME(sizeof(product_string)), product_string) <= 0) {
301303
// When udev enumeration or classification, we only got joysticks here, so no need to test
302304
if (enumeration_method != ENUMERATION_LIBUDEV && !class && !GuessIsJoystick(*fd)) {
303-
return false;
305+
goto error;
304306
}
305307

306308
// Could have vendor and product already from udev, but should agree with evdev
307309
if (ioctl(*fd, EVIOCGID, &inpid) < 0) {
308-
return false;
310+
goto error;
309311
}
310312

311313
if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) {
312-
return false;
314+
goto error;
313315
}
314316
}
315317

316318
name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string);
317319
if (!name) {
318-
return false;
320+
goto error;
319321
}
320322

321323
if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) &&
322324
SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) {
323-
SDL_free(name);
324-
return false;
325+
goto error;
325326
}
326327

327328
FixupDeviceInfoForMapping(*fd, &inpid);
@@ -331,14 +332,23 @@ static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *ve
331332
#endif
332333

333334
if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) {
334-
SDL_free(name);
335-
return false;
335+
goto error;
336336
}
337337
*name_return = name;
338+
*driver_return = driver;
338339
*vendor_return = inpid.vendor;
339340
*product_return = inpid.product;
340341
*guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0);
341342
return true;
343+
344+
error:
345+
if (driver) {
346+
SDL_free(driver);
347+
}
348+
if (name) {
349+
SDL_free(name);
350+
}
351+
return false;
342352
}
343353

344354
static bool IsSensor(const char *path, int *fd)
@@ -349,7 +359,7 @@ static bool IsSensor(const char *path, int *fd)
349359
SDL_zero(inpid);
350360
#ifdef SDL_USE_LIBUDEV
351361
// 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) &&
362+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
353363
!(class & SDL_UDEV_DEVICE_ACCELEROMETER)) {
354364
return false;
355365
}
@@ -422,6 +432,9 @@ static void FreeJoylistItem(SDL_joylist_item *item)
422432
SDL_free(item->mapping);
423433
SDL_free(item->path);
424434
SDL_free(item->name);
435+
if (item->driver) {
436+
SDL_free(item->driver);
437+
}
425438
SDL_free(item);
426439
}
427440

@@ -436,6 +449,7 @@ static void MaybeAddDevice(const char *path)
436449
struct stat sb;
437450
int fd = -1;
438451
char *name = NULL;
452+
char *driver = NULL;
439453
Uint16 vendor, product;
440454
SDL_GUID guid;
441455
SDL_joylist_item *item;
@@ -473,7 +487,7 @@ static void MaybeAddDevice(const char *path)
473487
SDL_Log("Checking %s", path);
474488
#endif
475489

476-
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) {
490+
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) {
477491
#ifdef DEBUG_INPUT_EVENTS
478492
SDL_Log("found joystick: %s", path);
479493
#endif
@@ -488,6 +502,7 @@ static void MaybeAddDevice(const char *path)
488502
item->path = SDL_strdup(path);
489503
item->name = name;
490504
item->guid = guid;
505+
item->driver = driver;
491506

492507
if (vendor == USB_VENDOR_VALVE &&
493508
product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
@@ -861,7 +876,7 @@ static void LINUX_ScanSteamVirtualGamepads(void)
861876
// Opening input devices can generate synchronous device I/O, so avoid it if we can
862877
class = 0;
863878
SDL_zero(inpid);
864-
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
879+
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
865880
(inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
866881
free(entries[i]); // This should NOT be SDL_free()
867882
continue;
@@ -2609,6 +2624,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26092624
SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target);
26102625
SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target);
26112626
#endif
2627+
} else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
2628+
// xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
2629+
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
2630+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
2631+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
2632+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
2633+
out->dpleft.kind = EMappingKind_Button;
2634+
out->dpright.kind = EMappingKind_Button;
2635+
out->dpup.kind = EMappingKind_Button;
2636+
out->dpdown.kind = EMappingKind_Button;
2637+
out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
2638+
out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
2639+
out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
2640+
out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
2641+
#ifdef DEBUG_GAMEPAD_MAPPING
2642+
SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target);
2643+
SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target);
2644+
SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target);
2645+
SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target);
2646+
#endif
2647+
}
26122648
}
26132649
}
26142650

@@ -2653,7 +2689,7 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26532689
#endif
26542690
}
26552691

2656-
if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
2692+
if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
26572693
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8
26582694
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] &&
26592695
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] &&
@@ -2674,17 +2710,38 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
26742710
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target);
26752711
#endif
26762712
}
2677-
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];
2713+
} else if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
2714+
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
2715+
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
2716+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
2717+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
2718+
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
2719+
out->right_paddle1.kind = EMappingKind_Button;
2720+
out->right_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
2721+
out->left_paddle1.kind = EMappingKind_Button;
2722+
out->left_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
2723+
out->right_paddle2.kind = EMappingKind_Button;
2724+
out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
2725+
out->left_paddle2.kind = EMappingKind_Button;
2726+
out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
26822727
#ifdef DEBUG_GAMEPAD_MAPPING
2683-
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
2728+
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY1)", out->right_paddle1.target);
2729+
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY2)", out->left_paddle1.target);
2730+
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY3)", out->right_paddle2.target);
2731+
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY4)", out->left_paddle2.target);
26842732
#endif
26852733
}
26862734
}
26872735

2736+
// Xbox Series controllers have the Share button as KEY_RECORD
2737+
if (joystick->hwdata->has_key[KEY_RECORD]) {
2738+
out->misc1.kind = EMappingKind_Button;
2739+
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
2740+
#ifdef DEBUG_GAMEPAD_MAPPING
2741+
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
2742+
#endif
2743+
}
2744+
26882745
// Cache the mapping for later
26892746
item->mapping = (SDL_GamepadMapping *)SDL_malloc(sizeof(*item->mapping));
26902747
if (item->mapping) {

0 commit comments

Comments
 (0)