Skip to content

joystick: Improve Xbox controller mapping with xpad quirks #13305

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions src/core/linux/SDL_udev.c
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ static bool SDL_UDEV_load_syms(void)

SDL_UDEV_SYM(udev_device_get_action);
SDL_UDEV_SYM(udev_device_get_devnode);
SDL_UDEV_SYM(udev_device_get_driver);
SDL_UDEV_SYM(udev_device_get_syspath);
SDL_UDEV_SYM(udev_device_get_subsystem);
SDL_UDEV_SYM(udev_device_get_parent_with_subsystem_devtype);
Expand Down Expand Up @@ -219,7 +220,7 @@ bool SDL_UDEV_Scan(void)
return true;
}

bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class)
bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver)
{
struct stat statbuf;
char type;
Expand Down Expand Up @@ -253,17 +254,27 @@ bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *pr

val = _this->syms.udev_device_get_property_value(dev, "ID_VENDOR_ID");
if (val) {
*vendor = (Uint16)SDL_strtol(val, NULL, 16);
inpid->vendor = (Uint16)SDL_strtol(val, NULL, 16);
}

val = _this->syms.udev_device_get_property_value(dev, "ID_MODEL_ID");
if (val) {
*product = (Uint16)SDL_strtol(val, NULL, 16);
inpid->product = (Uint16)SDL_strtol(val, NULL, 16);
}

val = _this->syms.udev_device_get_property_value(dev, "ID_REVISION");
if (val) {
*version = (Uint16)SDL_strtol(val, NULL, 16);
inpid->version = (Uint16)SDL_strtol(val, NULL, 16);
}

if (driver) {
val = _this->syms.udev_device_get_driver(dev);
if (!val) {
val = _this->syms.udev_device_get_property_value(dev, "ID_USB_DRIVER");
}
if (val) {
*driver = SDL_strdup(val);
}
}

class_temp = device_class(dev);
Expand Down
4 changes: 3 additions & 1 deletion src/core/linux/SDL_udev.h
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
#endif

#include <libudev.h>
#include <linux/input.h>
#include <sys/time.h>
#include <sys/types.h>

Expand All @@ -56,6 +57,7 @@ typedef struct SDL_UDEV_Symbols
{
const char *(*udev_device_get_action)(struct udev_device *);
const char *(*udev_device_get_devnode)(struct udev_device *);
const char *(*udev_device_get_driver)(struct udev_device *);
const char *(*udev_device_get_syspath)(struct udev_device *);
const char *(*udev_device_get_subsystem)(struct udev_device *);
struct udev_device *(*udev_device_get_parent_with_subsystem_devtype)(struct udev_device *udev_device, const char *subsystem, const char *devtype);
Expand Down Expand Up @@ -102,7 +104,7 @@ extern void SDL_UDEV_UnloadLibrary(void);
extern bool SDL_UDEV_LoadLibrary(void);
extern void SDL_UDEV_Poll(void);
extern bool SDL_UDEV_Scan(void);
extern bool SDL_UDEV_GetProductInfo(const char *device_path, Uint16 *vendor, Uint16 *product, Uint16 *version, int *class);
extern bool SDL_UDEV_GetProductInfo(const char *device_path, struct input_id *inpid, int *class, char **driver);
extern bool SDL_UDEV_AddCallback(SDL_UDEV_Callback cb);
extern void SDL_UDEV_DelCallback(SDL_UDEV_Callback cb);
extern const SDL_UDEV_Symbols *SDL_UDEV_GetUdevSyms(void);
Expand Down
103 changes: 80 additions & 23 deletions src/joystick/linux/SDL_sysjoystick.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,7 @@ typedef struct SDL_joylist_item
SDL_JoystickID device_instance;
char *path; // "/dev/input/event2" or whatever
char *name; // "SideWinder 3D Pro" or whatever
char *driver; // "xpad" or whatever
SDL_GUID guid;
dev_t devnum;
int steam_virtual_gamepad_slot;
Expand Down Expand Up @@ -274,54 +275,54 @@ static bool GuessIsSensor(int fd)
return false;
}

static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid)
static bool IsJoystick(const char *path, int *fd, char **name_return, Uint16 *vendor_return, Uint16 *product_return, SDL_GUID *guid, char **driver_return)
{
struct input_id inpid;
char *name;
char *name = NULL;
char *driver = NULL;
char product_string[128];
int class = 0;

SDL_zero(inpid);
#ifdef SDL_USE_LIBUDEV
// Opening input devices can generate synchronous device I/O, so avoid it if we can
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, &driver) &&
!(class & SDL_UDEV_DEVICE_JOYSTICK)) {
return false;
goto error;
}
#endif

if (fd && *fd < 0) {
*fd = open(path, O_RDONLY | O_CLOEXEC, 0);
}
if (!fd || *fd < 0) {
return false;
goto error;
}

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

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

if (ioctl(*fd, EVIOCGNAME(sizeof(product_string)), product_string) < 0) {
return false;
goto error;
}
}

name = SDL_CreateJoystickName(inpid.vendor, inpid.product, NULL, product_string);
if (!name) {
return false;
goto error;
}

if (!IsVirtualJoystick(inpid.vendor, inpid.product, inpid.version, name) &&
SDL_JoystickHandledByAnotherDriver(&SDL_LINUX_JoystickDriver, inpid.vendor, inpid.product, inpid.version, name)) {
SDL_free(name);
return false;
goto error;
}

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

if (SDL_ShouldIgnoreJoystick(inpid.vendor, inpid.product, inpid.version, name)) {
SDL_free(name);
return false;
goto error;
}
*name_return = name;
*driver_return = driver;
*vendor_return = inpid.vendor;
*product_return = inpid.product;
*guid = SDL_CreateJoystickGUID(inpid.bustype, inpid.vendor, inpid.product, inpid.version, NULL, product_string, 0, 0);
return true;

error:
if (driver) {
SDL_free(driver);
}
if (name) {
SDL_free(name);
}
return false;
}

static bool IsSensor(const char *path, int *fd)
Expand All @@ -349,7 +359,7 @@ static bool IsSensor(const char *path, int *fd)
SDL_zero(inpid);
#ifdef SDL_USE_LIBUDEV
// Opening input devices can generate synchronous device I/O, so avoid it if we can
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
!(class & SDL_UDEV_DEVICE_ACCELEROMETER)) {
return false;
}
Expand Down Expand Up @@ -422,6 +432,9 @@ static void FreeJoylistItem(SDL_joylist_item *item)
SDL_free(item->mapping);
SDL_free(item->path);
SDL_free(item->name);
if (item->driver) {
SDL_free(item->driver);
}
SDL_free(item);
}

Expand All @@ -436,6 +449,7 @@ static void MaybeAddDevice(const char *path)
struct stat sb;
int fd = -1;
char *name = NULL;
char *driver = NULL;
Uint16 vendor, product;
SDL_GUID guid;
SDL_joylist_item *item;
Expand Down Expand Up @@ -473,7 +487,7 @@ static void MaybeAddDevice(const char *path)
SDL_Log("Checking %s", path);
#endif

if (IsJoystick(path, &fd, &name, &vendor, &product, &guid)) {
if (IsJoystick(path, &fd, &name, &vendor, &product, &guid, &driver)) {
#ifdef DEBUG_INPUT_EVENTS
SDL_Log("found joystick: %s", path);
#endif
Expand All @@ -488,6 +502,7 @@ static void MaybeAddDevice(const char *path)
item->path = SDL_strdup(path);
item->name = name;
item->guid = guid;
item->driver = driver;

if (vendor == USB_VENDOR_VALVE &&
product == USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD) {
Expand Down Expand Up @@ -861,7 +876,7 @@ static void LINUX_ScanSteamVirtualGamepads(void)
// Opening input devices can generate synchronous device I/O, so avoid it if we can
class = 0;
SDL_zero(inpid);
if (SDL_UDEV_GetProductInfo(path, &inpid.vendor, &inpid.product, &inpid.version, &class) &&
if (SDL_UDEV_GetProductInfo(path, &inpid, &class, NULL) &&
(inpid.vendor != USB_VENDOR_VALVE || inpid.product != USB_PRODUCT_STEAM_VIRTUAL_GAMEPAD)) {
free(entries[i]); // This should NOT be SDL_free()
continue;
Expand Down Expand Up @@ -2609,6 +2624,27 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
SDL_Log("Mapped DPUP+DOWN to axis %d (ABS_HAT0Y)", out->dpup.target);
SDL_Log("Mapped DPLEFT+RIGHT to axis %d (ABS_HAT0X)", out->dpleft.target);
#endif
} else if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
// xpad will sometimes map the D-Pad as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
out->dpleft.kind = EMappingKind_Button;
out->dpright.kind = EMappingKind_Button;
out->dpup.kind = EMappingKind_Button;
out->dpdown.kind = EMappingKind_Button;
out->dpleft.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
out->dpright.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
out->dpup.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
out->dpdown.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped DPLEFT to button %d (BTN_TRIGGER_HAPPY1)", out->dpleft.target);
SDL_Log("Mapped DPRIGHT to button %d (BTN_TRIGGER_HAPPY2)", out->dpright.target);
SDL_Log("Mapped DPUP to button %d (BTN_TRIGGER_HAPPY3)", out->dpup.target);
SDL_Log("Mapped DPDOWN to button %d (BTN_TRIGGER_HAPPY4)", out->dpdown.target);
#endif
}
}
}

Expand Down Expand Up @@ -2653,7 +2689,7 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
#endif
}

if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
if (item->driver && SDL_strcmp(item->driver, "xpad") == 0) {
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY5 - BTN_TRIGGER_HAPPY8
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY5] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY6] &&
Expand All @@ -2674,17 +2710,38 @@ static bool LINUX_JoystickGetGamepadMapping(int device_index, SDL_GamepadMapping
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY8)", out->left_paddle2.target);
#endif
}

// The Xbox Series X controllers have the Share button as KEY_RECORD
if (joystick->hwdata->has_key[KEY_RECORD]) {
out->misc1.kind = EMappingKind_Button;
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
} else if (SDL_GetJoystickVendor(joystick) == USB_VENDOR_MICROSOFT) {
// The Xbox Elite controllers have the paddles as BTN_TRIGGER_HAPPY1 - BTN_TRIGGER_HAPPY4
if (joystick->hwdata->has_key[BTN_TRIGGER_HAPPY1] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY2] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY3] &&
joystick->hwdata->has_key[BTN_TRIGGER_HAPPY4]) {
out->right_paddle1.kind = EMappingKind_Button;
out->right_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY1];
out->left_paddle1.kind = EMappingKind_Button;
out->left_paddle1.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY2];
out->right_paddle2.kind = EMappingKind_Button;
out->right_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY3];
out->left_paddle2.kind = EMappingKind_Button;
out->left_paddle2.target = joystick->hwdata->key_map[BTN_TRIGGER_HAPPY4];
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
SDL_Log("Mapped RIGHT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY1)", out->right_paddle1.target);
SDL_Log("Mapped LEFT_PADDLE1 to button %d (BTN_TRIGGER_HAPPY2)", out->left_paddle1.target);
SDL_Log("Mapped RIGHT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY3)", out->right_paddle2.target);
SDL_Log("Mapped LEFT_PADDLE2 to button %d (BTN_TRIGGER_HAPPY4)", out->left_paddle2.target);
#endif
}
}

// Xbox Series controllers have the Share button as KEY_RECORD
if (joystick->hwdata->has_key[KEY_RECORD]) {
out->misc1.kind = EMappingKind_Button;
out->misc1.target = joystick->hwdata->key_map[KEY_RECORD];
#ifdef DEBUG_GAMEPAD_MAPPING
SDL_Log("Mapped MISC1 to button %d (KEY_RECORD)", out->misc1.target);
#endif
}

// Cache the mapping for later
item->mapping = (SDL_GamepadMapping *)SDL_malloc(sizeof(*item->mapping));
if (item->mapping) {
Expand Down
Loading