atar-axis / xpadneo

Advanced Linux Driver for Xbox One Wireless Controller (shipped with Xbox One S)
https://atar-axis.github.io/xpadneo/
GNU General Public License v3.0
1.92k stars 111 forks source link

Re-implement hidraw patching #286

Open kakra opened 3 years ago

kakra commented 3 years ago

Note to myself: xpadneo should probably redo how we patch the hidraw interface, this may fix some of the problems:

The first Xbox controller had a simple button mapping with 10 (or 11) consecutive bits for buttons, both SDL and games assumed exactly that:

Bit       0  1  2  3  4  5  6   7   8  9  (10)
Button    A  B  X  Y  LB RB Sel Str LS RS (Gui)

Games simply count buttons, and SDL did that too. Everything worked fine. The Guide button (Gui) is actually button 11 and not supposed to be used by games. It was still in the bitmap but in later controllers, MS would remove the button from the bitmap. It is meant to be read by the OS only anyways, for the sole purpose getting back to a global menu or dashboard.

But then came the Bluetooth-enabled Xbox controller which were supposed to support Android. As Android is based on Linux and had no special driver for the gamepad, MS had to use a sparse bitmap to skip the buttons from the Linux event interface that are missing on the controller:

Bit       0  1 (2) 3  4 (5) 6  7  8   9    10  11 (12)  (13)
Button    A  B (C) X  Y (Z) LB RB Sel Str  LS  RS (Gui) (Shr)

As you can see, it counts a C and Z button which the controller does not have. But it makes Android happy as each bit maps to the correct event number the kernel expects, and Android games actually use event numbers instead of counting bit positions. So the controller does work there but has 12 buttons (or even 13 including the Share button which does not even map properly to a Linux event number because Linux has no such button in its description). Due to how the Linux event interface maps bits to buttons, games would actually see 15 buttons, see below.

The Guide button is actually no longer part of this bitmap in later firmware versions of the controller - probably because it messes the ordering of buttons Linux games expect:

Position  0  1  2  3  4  5  6  7  8   9    10  11  12    13  14
Event     A  B  C  X  Y  Z  LB RB LB2 RB2  Sel Str Gui   LS  RS

This is what the Linux kernel expects when mapping bits to button events. A sane controller would offer a sparse bitmap, and everything maps properly, and software using the event symbols would be happy. Unfortunately, the HID descriptor has no way of saying which buttons do actually exist, so games would see 15 buttons now (with the Share button mapping at a strange position). But that's a minor cosmetic issue at that layer.

If you look at the ordering of buttons, this is completely out of order: Games back that day expected 10 buttons, counting from position 1, so without a proper driver removing buttons C, Z, LB2, RB2, and Gui from the position list, games are still messed up.

Also, the old joydev interface (/dev/js*) did the same dumb thing as games and simply counts positions, so you would see 15 buttons, where only A and B are in correct position, and all the other buttons are unreachable, misplaced, or dead. Games that could not re-assign buttons for gamepads (or didn't expect more than 10 buttons) would not work. And then, you'd have to repeat the process for every game.

So xpadneo stepped in and fixed that: Removing the dead positions and re-assigning the event symbols, so we ended up with this order:

A,B,X,Y,LB,RB,Select,Start,Guide,LS,RS

Hmm? Yes, that looks correct you might say. But it isn't: Games expect the first 10 buttons to be gamepad buttons, Guide does not belong there, if anything it belongs into the last position. But the Linux kernel would not let us do that: It always orders button positions by the event number, and Guide comes before LS/RS in that list. :-(

So xpadneo removed the Guide button, as Microsoft also did in newer firmware versions. Now the gamepad has 10 buttons for games. Instead, both xpadneo and MS moved the Guide button to a separate input application so applications could still read it. Unfortunately, Steam simply ignores that.

So, xpadneo did its thing and properly made the controller work when SDL stepped in to redo this for the models we support (probably because Bluetooth support for the controller has been added to hid-microsoft in the kernel).

SDL sits between the Linux event interface and a game and re-assigns each position a new position, it doesn't even look at event symbols. And this is where we end in a big mess: Suddenly, Y would become X because SDL thinks that the third position is a blind button C - even when using xpadneo. If it looked at the event name instead, everything would work. It actually uses its community-updated controller db to match position back to button symbols (not event names!), which it then exposes to games in the correct expected order (as initially mentioned in the very first table).

So we added a hack to xpadneo which would bypass the controllerdb by pretending we would be a model that SDL does not support. SDL then just falls back to a generic controller layout it calls "XInput Controller" which matches the very first table - exactly what we expose (minus the Guide button because it would have a wrong event name otherwise, if we include it, buttons Guide/LS/RS are swapped).

So what you actually want is that SDL does not detect the controller, or uses a mapping from its table that matches exactly a:b0,b:b1,x:b2,y:b3,leftshoulder:b4,rightshoulder:b5,back:b6,start:b7,leftstick:b8,rightstick:b9,(guide:b10).

With current SDL and xpadneo, xpadneo maps to exactly one such entry but the Guide button will be dead (we send it as a keyboard event instead from the consumer control page, I wish Steam would simply add support for that, it would have no conflicts with existing configurations).

I'm not sure how Steam Link works: It may use SDL, then everything should work unless an awkward controllerdb entry is sitting somewhere.

SDL gained some support for using hidraw devices lately, which messes with a lot of the above assumptions, and Steam/Proton may detect the gamepad twice as two different devices, one with correct mappings (evdev) and one with wrong mappings (hidraw). I fixed that lately with two commits: One prevents SDL to find the gamepad twice (it's a bugfix in xpadneo), and one removes user access to hidraw (which works around the SDL issue).

Originally posted by @kakra in https://github.com/atar-axis/xpadneo/issues/244#issuecomment-813046479

kakra commented 2 years ago

More issues: