Why your Xbox 360 wireless controller's D-pad is now wrong and how to fix it
Note: by its nature, the fixes here (other than waiting for updates) require you to be comfortable enough with the command line to follow along. I hope I've at least written the explanation of why this has happened clearly enough to not need any deep knowledge though.
If you're one of the few people like me still using an Xbox 360 wireless controller on your Linux PC, you may have recently noticed your D-pad going all wrong. Left is up, up is left, right is down, and down is right. This will probably have coincided with getting a new kernel, starting with 6.17.
Why it's wrong
For some reason, back in the day, when Xbox 360 controller support was being added to the Linux kernel, wired controllers mapped the D-pad as axes on a hat, and wireless controllers mapped them as separate buttons. For some reason, the D-pad buttons were given BTN_TRIGGER_HAPPY1 to BTN_TRIGGER_HAPPY4. (It seems that the HAPPY events are generic joystick events, rather than meaning anything specific, which answers a question I've been vaguely wondering about for a long time.)
In Linux 6.17, this was changed to report BTN_DPAD events. This is all good and makes sense. Now the controller is actually reporting what's being pressed, not some arbitrary button presses you need to know how to map.
Unfortunately/fortunately (depending on how you look at it), we now have SDL, the layer that basically everything game-related on Linux uses. It has a specific hardcoded mapping for the Xbox 360 wireless controller, plus several more entries in a more general controller database for good measure. This would be fine, but the change to the kernel has also changed the order in which SDL sees the buttons. Thankfully this has only affected the D-pad though, rather than changing all of the buttons around.
However, good news! As of January 2016, the kernel driver has fixed the weirdness with wireless controllers by reporting both a hat and the buttons. More good news! SDL has functionality to automatically generate a mapping for a game controller, and it works with both the old xpad code and the new one. We just need to get rid of the old mappings which don't work any more.
How to fix it
(This has been rewritten a bit from the first version of this blog post as I learned more)
In terms of fixing this out of the box, I've already submitted pull requests to SDL (SDL3, SDL2), which were merged, but then the SDL2 one was reverted until there's more testing with SDL3. This does unfortunately mean that Xbox 360 wireless controllers will continue to be broken with newer Linux kernels, even with new releases using SDL2, but hopefully the SDL2 change can go back in before too much longer.
Unfortunately, even once SDL has a release, and Valve and your distro have packaged it, that still won't magically fix everything without any work. Games tend to ship their own libSDLs, or even have it statically compiled in, rather than relying on Valve's or the OS's. On the plus side, once SDL has been updated (or if you manually remap the controller in Steam), games in Steam should be fine if you're using Steam Input – that kicks the controller handling up the stack slightly, and so they should transparently start to work properly again.
There are, of course, games and applications using SDL that aren't Steam-based, plus it'll still be a while until SDL is updated. I have three possible solutions for that, two of which don't rely on SDL being updated.
Option 1 – Specifying your own mapping
As long as you're using Steam Input for Steam games, this section should really cover you for pretty much everything.
There are a few ways of getting your own controller mappings into games. The easiest and most reliable option is to set the SDL_GAMECONTROLLERCONFIG environment variable, e.g.:
export SDL_GAMECONTROLLERCONFIG="0300a81c5e040000a102000000010000,Xbox 360 Wireless Controller,a:b0,b:b1,x:b2,y:b3,back:b6,guide:b8,start:b7,leftstick:b9,rightstick:b10,leftshoulder:b4,rightshoulder:b5,dpup:b11,dpdown:b12,dpleft:b13,dpright:b14,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,"
Unfortunately, I've had issues getting this to make its way through the Steam runtime. I'm sure it's possible, I just don't know how to do it, and a quick web search didn't help me. On the other hand, launching Steam with this environment variable set does fix Steam itself, so Steam Input now shows the correct D-pad directions without having to go in and manually remap them!
The other alternative is to put the same kind of configuration in a file called gamecontrollerdb.txt in the game's directory. This isn't inherently loaded by SDL, but most games seem to pick up on it.
In both cases, the third-party SDL_GameControllerDB has information on how to generate these mappings, although my one above should work for you.
Option 2 – Deleting or replacing the old libSDL
This works once you've either built a new libSDL yourself, or your distro/Valve have updated theirs. For games that bundle their own libSDL, you should be able to either delete it or replace it with a newer copy. That's all there really is to say about this one. Unfortunately quite a few games (including those built with the Godot engine, for example) statically link SDL, which means there is no separate library to trivially replace.
Option 3 (cursed) – Edit the binaries to fix the mapping
For statically linked binaries (and old versions of libSDL, if you feel like it), I've realised that there is a somewhat cursed way of doing it. I do not provide any guarantees that this is a sensible thing to do. It is definitely using the wrong tool for the job. However, with the controller mapping being text, and both the old and the new D-pad buttons having double-digit numbers, you can run the following sed command on the relevant game binary (try grepping for the original string if you don't know which one is the relevant binary):
sed -i.bak 's/a:b0,b:b1,back:b6,dpdown:b14,dpleft:b11,dpright:b12,dpup:b13,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,/a:b0,b:b1,back:b6,dpdown:b12,dpleft:b13,dpright:b14,dpup:b11,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,/g <filename>
This does, however, have one potential casualty: the “Razer Onza Classic Edition”. If you have one of these and it's working fine, you might want to be more selective.
The other option
Alternatively you can skip all of this headache and give yourself an entirely different headache by manually building the old xpad driver against your newer kernels. This is also a viable option. I don't know if it's a more or a less tedious option than the alternative.