A Weird Imagination

Emulating Xbox controllers on Linux

Posted in

The problem#

The Xbox 360 controller has become the defacto standard controller in PC gaming in recent years, likely due to both the popularity of the Xbox and the fact that the controller can easily be used with a computer. One downside of this is that some games assume you have one. If the game supports it and is running through Steam, then Steam's controller settings will let you use any controller, but that doesn't work for all games, and you might not be using Steam. The game that prompted this blog post actually does have Steam controller support promised in the future, but it's in early access and they are busy developing other parts of the game.1

xboxdrv#

The solution is xboxdrv, the userspace Xbox controller driver. In addition to supporting actual Xbox controllers, it can also simulate Xbox controllers based on inputs from other devices like a PlayStation controller or some less common controller.

Just do something like

#!/bin/sh
echo -n "Press a button on the PSOne controller... "
evdev="$($(dirname "$0")/identify_evdev.py)"
echo using device "$evdev".
xboxdrv --evdev "$evdev" \
    --evdev-absmap ABS_X=x1,ABS_Y=y1,ABS_RZ=x2,ABS_Z=y2 \
    --axismap -Y1=Y1,Y2=X2,-X2=Y2 \
    --evdev-keymap BTN_TOP=x,BTN_TRIGGER=y,BTN_THUMB2=a,BTN_THUMB=b,BTN_BASE3=back,BTN_BASE4=start,BTN_BASE=lb,BTN_BASE2=rb,BTN_TOP2=lt,BTN_PINKIE=rt,BTN_BASE5=tl,BTN_BASE6=tr,BTN_DEAD=dl,KEY_#300=du,KEY_#301=dr,KEY_#302=dd \
    --mimic-xpad --silent --quiet

and you're done. Easy, right?

Well, no. The first few lines are a pretty straightforward use of identify_evdev.py to discover the right device to use. But the big lists of axis and button mappings are not at all obvious and depend on your actual controller setup. So I made a script for generating them.

Generating button maps#

create_xboxdrv_evdev_map.py generates that xboxdrv command by listing the Xbox buttons and axes and asking the user to touch the corresponding button or axis on their controller, just like you would expect in any input configuration screen. The code is implemented in Python using the evdev library and is well commented.

To use it, just run the command and follow the directions. When done, the final line output will be an invocation of xboxdrv that you can save and run any time you want to use that controller:

$ git clone https://github.com/dperelman/gamepad-util.git
$ gamepad-util/create_xboxdrv_evdev_map.py
Press any button on only the joystick you are setting up.
Selected event device: /dev/input/event22
Stop pressing any buttons.
Press the corresponding button on your controller. If the button doesn't exist, press the start button again to ignore it.
Press start: BTN_START
Press back (or select): BTN_SELECT
Press guide (large center button): (none)
...
Press trigger lt (left analog trigger (L or L2 button)) all the way: ABS_Z
Press trigger rt (right analog trigger (R or R2 button)) all the way: ABS_RZ
xboxdrv --evdev "/dev/input/event22" --evdev-keymap "BTN_A=a,BTN_B=b,BTN_TL=lb,BTN_THUMBR=tr,BTN_SELECT=back,BTN_START=start,BTN_THUMBL=tl,BTN_TR=rb,BTN_WEST=y,BTN_NORTH=x" --evdev-absmap "ABS_RZ=rt,ABS_RY=y2,ABS_RX=x2,ABS_Z=lt,ABS_Y=y1,ABS_X=x1" --axismap "-y2=y2,-y1=y1" --mimic-xpad --silent

y-axis confusion#

One of the most confusing parts of figuring out how to generate these mappings was the y-axis. jstest was showing one thing and the debug output from xboxdrv was showing a different value, which made no sense:

$ sudo xboxdrv --detach-kernel-driver --mimic-xpad
...
X1: -2288 Y1:-32768  X2: -2879 Y2:   887  du:0 dd:0 dl:0 dr:0  back:0 guide:0 start:0  TL:0 TR:0  A:0 B:0 X:0 Y:0  LB:0 RB:0  LT:  0 RT:  0

$ jstest /dev
...
Axes:  0: -2178  1: 32767  2:-32767  3:     0  4:     0  5:-32767  6:     0  7:     0 Buttons:  0:off  1:off  2:off  3:off  4:off  5:off  6:off  7:off  8:off  9:off 10:off

The differences between the X1 on xboxdrv and axis 0 on jstest can be explained by them simply printing at different times as the joystick was still moving. On the other hand, xboxdrv shows Y1 as -32768 while jstest shows axis 1 as 32767. Even though the two clearly change together, they are always opposite.

Eventually I solved the mystery by delving into the source code of xboxdrv: it inverts the y-axis just before emitting it to uinput, but after it prints the value to the screen. Which is why create_xboxdrv_evdev_map.py asks for the y-axis in reverse order: as a simple way of inverting it back.

Non-standard key names#

Some of the buttons mapped have names, albeit weird ones like BTN_DEAD while others just have numbers like KEY_#300. The names are listed in input.h in the Linux kernel, and there are some numbers that lack corresponding names like 300 which is 0x12c in hexadecimal. xboxdrv works around that by supporting the KEY_#300 format so it can reference unnamed buttons.

--mimic-xpad#

There is a kernel driver for Xbox controllers called xpad which has different default mappings. All of the xboxdrv invocations in this blog post use --mimic-xpad to make xboxdrv act like xpad which some games expect. When using xboxdrv with an actual Xbox controller, the command to run is

$ sudo xboxdrv --silent --detach-kernel-driver --mimic-xpad

--silent disables debugging output and --detach-kernel-driver makes sure xpad isn't also handling the controller.


  1. The game is Assault Android Cactus, which I highly recommend. It is a twin-stick shooter with some bullet hell elements and support for up to 4 player co-op. 

Comments

Have something to add? Post a comment by sending an email to comments@aweirdimagination.net. You may use Markdown for formatting.

There are no comments yet.