Configuring a gamepad manually for Android on Chrome OS

In order to play Android games on Chrome OS with a wireless Xbox 360 gamepad I found that, while the gamepad worked well in some games, for others it seemed that some configuration was required.

I wanted to play the game "War Robots" but when I opened it up I discovered that, while I could move and aim, the buttons to fire were incorrectly mapped.

First of all, I tried creating /system/usr/keylayout/Vendor_045e_Product_0719.kl on the Android filesystem, as this was what had worked for me to configure this gamepad on my Nexus 9. However, editing the Android keylayout didn't seem to do anything on the Chromebook. I had a look around the device and found that in the list of CrOS installable packages at /etc/portage/make.profile/package.installable, the following was included: games-util/joystick-1.4.2.

Accordingly, I emerged joystick-1.4.2 and played around with jstest and jscal.

The set of commands to emerge these binaries:

sudo su -
emerge joystick

Turned out I that with this I was able to easily swap gamepad button mappings around.

First of all I dumped the current mapping with

jscal -q /dev/input/js0

which gave the following output
jscal -u 6,0,1,2,3,4,5,15,304,305,307,308,310,311,314,315,316,317,318,704,705,706,707 /dev/input/js0

In order to determine which number corresponded to which button, I tried

jstest /dev/input/js0

However, when I pressed some buttons on the gamepad, numbers for the buttons were displayed which did not correspond with the set above. So I tried evtest

emerge evtest



No device specified, trying to scan all of /dev/input/event*
Available devices:
/dev/input/event0:      cros_ec
/dev/input/event1:      ROCKCHIP-I2S Headset Jack
/dev/input/event2:      RockchipHDMI HDMI Jack
/dev/input/event3:      gpio-keys.10
/dev/input/event4:      Elan Touchpad
/dev/input/event5:      Elan Touchscreen
/dev/input/event6:      Xbox 360 Wireless Receiver
/dev/input/event7:      Xbox 360 Wireless Receiver
/dev/input/event8:      Xbox 360 Wireless Receiver
/dev/input/event9:      Xbox 360 Wireless Receiver
Select the device event number [0-9]:


Hooray! Pressing the buttons on the gamepad gives useful output, such as

code 311 (BTN_TR), value 1
code 315 (BTN_START), value 1

and so on

Armed with this information I was easily able to fix the mapping in War Robots, which had one "Fire" button mapped to "Start" and another "Fire" button mapped to "Back". I found a number of reports online suggesting that this is a current issue with (lack of) controller support in the game itself, rather than any quirk of running Android on Chrome OS.

jscal original dump of mappings:
jscal -u 6,0,1,2,3,4,5,15,304,305,307,308,310,311,314,315,316,317,318,704,705,706,707 /dev/input/js0
Mod for War Robots;
swapped Start (315) with R1 (311) and Back (314) with L1 (310):
jscal -u 6,0,1,2,3,4,5,15,304,305,307,308,314,315,310,311,316,317,318,704,705,706,707 /dev/input/js0
Opened up the game. Now L1 and R1 are the fire buttons!

Saved the config. to a file with:

jscal -q /dev/input/js0 > /usr/local/

Unfortunately I also had another issue to fix. There is a fair amount of play in both the analog axis of my controller, the right one particularly, meaning that with the controller at rest, it's sometimes still pointing to the right slightly. In order to stop the annoying 'camera moving to the right without me moving it' issue, I needed to calibrate the dead zones.

Back to jscal:

jscal -c

(went through the calibration, which involves pressing each button/moving each axis once)


Calibrated axis:

Setting correction to:
Correction for axis 0: broken line, precision: 0.
Coeficients: -338, -338, 16555, 16217
Correction for axis 1: broken line, precision: 0.
Coeficients: -8, -8, 16388, 16380
Correction for axis 2: broken line, precision: 0.
Coeficients: 126, 126, 4260750, 4161663
Correction for axis 3: broken line, precision: 0.
Coeficients: -64, -64, 16416, 16352
Correction for axis 4: broken line, precision: 0.
Coeficients: 55, 55, 16357, -16357
Correction for axis 5: broken line, precision: 0.
Coeficients: 111, 128, 4836527, 4227201

Dumped the above corrections with

jscal -p /dev/input/js0
which gave the following output:
jscal -s 6,1,0,-338,-338,16555,16217,1,0,-8,-8,16388,16380,1,0,126,126,4260750,4161663,1,0,-64,-64,16416,16352,1,0,55,55,16357,-16357,1,0,111,128,4836527,4227201 /dev/input/js0
Dumped it to a file:

jscal -p /dev/input/js0 > /usr/local/

First attempt at editing dead zones.


Original file contents (as above):

jscal -s 6,1,0,-338,-338,16555,16217,1,0,-8,-8,16388,16380,1,0,126,126,4260750,4161663,1,0,-64,-64,16416,16352,1,0,55,55,16357,-16357,1,0,111,128,4836527,4227201 /dev/input/js0

Changed the 4th and 5th values (axis 0) and the 22nd and 23rd values (axis 3) from -338,-338 and -64, -64 respectively to +/- ~3000 (Axis 0 and 3 are L-<  >+R on the two analogs).

New file contents:
jscal -s 6,1,0,-3038,3038,16555,16217,1,0,-8,-8,16388,16380,1,0,126,126,4260750,4161663,1,0,-3064,3064,16416,16352,1,0,55,55,16357,-16357,1,0,111,128,4836527,4227201 /dev/input/js0
sh /usr/local/

Opened up the game. Still some drift in the right-hand analog. Let's have a look at what's going on with jstest:

jstest /dev/input/js0

Moving the analogs different ways, dropping them to center, and watching the values change in jstest confirms that while there is play in both sticks, the right hand side of the right hand stick is particularly loose.

Second attempt at modifying the calibration

vi /usr/local/

Tried -3064,10064 on axis 3.

sh /usr/local/

Opened up the game. This one works quite well! I probably set it a little too high, but at least there's no right hand drift at all, now.

Final file contents:
jscal -s 6,1,0,-3038,3038,16555,16217,1,0,-8,-8,16388,16380,1,0,126,126,4260750,4161663,1,0,-3064,10064,16416,16352,1,0,55,55,16357,-16357,1,0,111,128,4836527,4227201 /dev/input/js0  
Added a shebang as it appears that a rule can be added to udev on other Linux systems, and the calibration file placed in /usr/local/bin, meaning that it would auto-calibrate whenever the gamepad is plugged in. I would expect this to would work on Chrome OS also, though I imagine it'd get wiped out when the system auto-updates. For now, I'll just pop open a shell and run sh /usr/local/ when I want to play Android games with the Xbox 360 joypad, and, similarly, sh /usr/local/ when I want to play War Robots. 

Having tested with a few other games, it turns out that after manually editing the dead zone values, the next pair of values also needs to be changed in order for the full range of stick movement to be correctly recognised. Not an issue in WR, but it would be in some games. Testing the gamepad in "Modern Combat 5", I found that in addition to changing the dead zone value pairs to eliminate camera drift, I also had to increase the next set of values by a few thousand, in order for the game to recognize when I had moved the right analog stick all the way to the top/bottom/sides. (confirmed in jstest)

The following config seems to work well in MC5 to eliminate camera drift while still passing maximum values reasonably close to the original. It still doesn't quite give the proper maximum range, but seems close enough to work well for this game.
jscal -s 6,1,0,237,237,16266,16503,1,0,797,797,16153,16792,1,0,126,126,4260750,4161663,1,0,-3908,3908,18086,18694,1,0,-6989,6989,19894,19904,1,0,122,125,4400447,4129650 /dev/input/js0
My setup could use some further editing/fine tuning in order to achieve the best operating results (optimize the dead zones even more, could do with adding a "Jump" button in WR), but I'm happy enough with the config as is at the moment. 

I imagine that for other 360 gamepads needing to be configured, a slightly different set of values would probably need to be applied as, depending on the state of the analog sticks, the dead zone areas would most likely differ somewhat.

TL;DR version:
It's possible to change the button mappings on a 360 gamepad on Chrome OS by emerging joystick-1.4.2, dumping the values with jscal -q, changing some values, and applying them with jscal -u. Similarly, analog stick dead zone calibration can be dumped and applied with jscal -p and jscal -s respectively. The changes won't persist after the gamepad is removed, but by saving the edited values to a shell script, the fix is convenient to reapply. 


  1. hey nolirium can you help me? the controller i have controls the keyboard on my chromebook not the games :/ how do i fix?

    1. could we do a hangouts chat maybe?

    2. I don't have much time for extended Hangouts chats or anything like that really, tbh

  2. Does it show up correctly (as a gamepad) on

    1. no it does nothing :/ is that bad? im assuming it is :/

    2. no gamepad it doesnt react at all

    3. Which controller? You might be out of luck right now, I think they're in the middle of a rewrite of some of the underlying code dealing with gamepads. I saw a post from one of the Google devs recently suggesting that people get their Bluetooth controllers from somewhere with a decent return policy...

      You could do dev_install & emerge evtest or joystick/jstest as per my post above, and see if anything shows up there

    4. its a obecome t3 bluetooth controller i was messing arond with jscal/jstest and emerging random things that looked like they could help me could we please do a hangout call it would make this thing so much easier it doesnt have to be long either and im seeing people do the games im trying to play like mc5 heres the output from evtest

    5. yeah, sorry, been a bit busy. Did you have any success? If not, if you install the Android app 'Game Controller Keymapper', does it also show up there as a keyboard?

    6. no it didnt work
      the app says "Chromeoskeyboard"

    7. Saw something that reminded me of this. If you still have this problem you could try adding a rule in udev, similar to the 8bitdo rules listed here:

      Just one of the lines, saved as something like 99-gamepad-rules in /etc/udev/rules.d/ might do the trick. (change ATTRS{name} to whatever shows up in evtest or lsusb as the name of your controller).

      SUBSYSTEM=="input", ATTRS{name}=="whateverthenameis", MODE="0666", ENV{ID_INPUT_JOYSTICK}="1"

  3. could we plz do a hangout chat though?