Configuring a gamepad manually for Android on Chrome OS


UPDATE: 

As of recent versions of Chrome OS, the procedure detailed below (using jscal) no longer works to remap keys for Android apps. The original post should now be considered deprecated and no longer applicable. 

 In some cases, Android apps will suffice to remap keys, and in other cases it may be possible to remap events in CrOS via evdev.


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:


shell

sudo su -
dev_install
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

evtest

Output:


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]:

6


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/WR_gamepad.sh


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)


Output:


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/jscal.sh


First attempt at editing dead zones.


vi/usr/local/jscal.sh


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
:wq!
sh /usr/local/jscal.sh

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/jscal.sh


Tried -3064,10064 on axis 3.


:wq!

sh /usr/local/jscal.sh

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:

#!/bin/bash
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/jscal.sh when I want to play Android games with the Xbox 360 joypad, and, similarly, sh /usr/local/WR_gamepad.sh 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.

#!/bin/bash 
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. 

27 comments:

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

    ReplyDelete
    Replies
    1. could we do a hangouts chat maybe?

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

      Delete
  2. Does it show up correctly (as a gamepad) on html5gamepad.com?

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

      Delete
    2. no gamepad it doesnt react at all

      Delete
    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

      Delete
    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

      Delete
    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?

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

      Delete
    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:

      http://g00se.org/2016/07/8bitdo-udev.html

      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).
      e.g.

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

      Delete
  3. could we plz do a hangout chat though?

    ReplyDelete
  4. I am trying to configure a wired xbox 360 controller to map buttons for PUBG Mobile. Its been a difficult task. Even with SuperSu running on my Chomebook, I haven't had any luck allowed new .ki files to be used for keymapping via Tincore Keymapping or Game Controller KeyMapper. Something about the Kernal not supporting UINPUT. Any thoughts?

    ReplyDelete
    Replies
    1. You should be able to use http://appmirror.net/app/tools/gamekeyboard/ for this. I just tested it out with my wireless Xbox 360 pad and after setting it up it works well with PUBG Mobile.

      I may make another post laying out the steps in a bit more detail but briefly:

      I downloaded GameKeyboard+ from the link above, opened it, tapped "Settings", under "General Usage Settings" unchecked Standalone Mode, then reopened the app - it showed three prompts for setup/first time running under IME mode.

      Step 1 (enable GameKeyboard+ IME) would normally open Android's screen to enable IMEs, on CrOS this setting routes to Chrome's Language Settings screen, so as per my blog post regarding IMEs I had to enable the IME manually in the shell.

      After enabling the IME "com.locnet.gamekeyboard2/.SoftKeyboard" in a root Android shell, and setting it as default (Step 2), I was able to select the four right hand soft buttons on GameKeyboard+'s touchscreen overlay to bind to A, B, X and Y on my 360 controller, bind them, remove the other soft buttons, open PUBG, in PUBG's settings move the right hand buttons (Fire etc.) slightly so they were well aligned with the GameKeyboard+ overlay, then start the game.

      Delete
  5. Hi! I'm also trying to configure my Shield TV controller for PUBG Mobile, but I have problems with the keymapping. Basically both triggers are recognized as axis and also control the right stick. Since the chromebook thinks that the axis is centered when the trigger is half pressed, this results in camera spin, even when the triggers are not pressed at all.
    I was trying to change the keymap with your guide, but couldn't figure out the specifics.
    This problem appears only on chrome os, Android recognizes the controller correctly, windows recognizes it as an Xbox 360 controller, but the mapping is correct (the Shield controller is basically an Xbox controller).
    Any ideas how I could unbind the triggers form the axis and map it correctly?

    ReplyDelete
    Replies
    1. Although you may be able to fix the bindings with e.g. jscal, I think it might be more straightforward to try adding in a keylayout file for your controller to the Android /system/usr/keylayout dir.

      If you haven't got a R/W Android system in CrOS (per the rooting scripts), you could, in the CrOS shell, copy the Android keylayouts directory to e.g. /usr/local, add your .kl file, bind mount keylayouts back across to its original location, and then reboot (just) the Android container (with printf reboot | android-sh, again in the CrOS shell, as root).

      You can check the Vendor and Product ID for your controller with lsusb, then maybe find the relevant .kl online, or rename a copy of a similar .kl to correspond with the Vendor and Product ID of your controller. Perhaps you already have the relevant .kl in your other Android device's keylayout directory (though not necessarily)...

      From looking at the stock CrOS keylayout file, the listing for the axes of generic gamepads is

      axis 0x00 X
      axis 0x01 Y
      axis 0x02 Z
      axis 0x03 RX
      axis 0x04 RY
      axis 0x05 RZ

      while the x360 (Vendor_045e_Product_028e.kl) controller axes have this mapping:

      axis 0x00 X flat 4096
      axis 0x01 Y flat 4096
      axis 0x03 Z flat 4096
      axis 0x04 RZ flat 4096

      # Triggers.
      axis 0x02 LTRIGGER
      axis 0x05 RTRIGGER

      So yeah, it looks like 0x02 and 0x05 axes events are sent by the controller, and mapped to Z and RZ (right stick) in the CrOS Android container's stock keylayout for generic/unspecified controllers.

      I found an old NVidia Shield controller's .kl online

      axis 0x00 invert X flat 2
      axis 0x01 Y flat 2
      axis 0x02 invert LTRIGGER flat 2
      axis 0x03 invert Z flat 2
      axis 0x04 RZ flat 2
      axis 0x05 invert RTRIGGER flat 2

      More or less the same mappings as x360, but different deadzones etc. So, if you can't find the .kl that exactly matches your controller's Vendor/Device ID, the Nvidia may be better to use as a base, I guess.

      The latter mappings are from a .kl for the original Shield controller, downloaded from this xda thread:

      https://forum.xda-developers.com/showthread.php?t=2754866

      If the .kl method doesn't work, or you would prefer to try doing it another way, feel free to post back.

      Delete
  6. Thanks! I can't seem to find the the .kl file for the Shield Controller 2017 anywhere online. But If I have paired my phone with the controller, I should be able to find the corresponding .kl file on it? Because I'm not able to find the file with the corresponding vendor ID. Is there a way to bluetooth vendor IDs (or it should be the same)?
    Right now i'm trying to rename Xbox .kl file, just to see what happens, but I don't fully understand how to bind the file (I have root access with your script).

    ReplyDelete
    Replies
    1. Ok, disregard (parts of) my previous comment. I was able to get the correct nvidia .kl files off my Shield TV (small miracle, since i'm not home). How can I get them into the Chome OS keylayout folder?

      Delete
    2. Oh, if you have root access (su in Android) from the rooting script, then you shouldn't need to bind mount anything since you should already have a writeable Android rootfs. Your .kl should go into the directory /opt/google/containers/android/rootfs/root/system/usr/keylayout/

      The following part may not be necessary at all, but it's possible that the permissions, ownership, context of the file you add may need to match those of the existing files in the directory. To rule out any potential issue with this, what I would do is make a duplicate copy of an existing file in the destination directory (with cp -a), then copy (overwrite) the contents of my file into the duplicate.

      In case the above paragraph is a little unclear, here's an example of what I mean: http://pasted.co/52ccc1cf

      Delete
    3. Hm. Did what you described and it still doesn't work.
      Is Chrome OS even using these as standard? Because I use html5gamepad.com to troubleshoot and I don't think that accesses the android layouts. I also use the Game Controller KeyMapper app and there the gamepad shows up as "ChromeOS Gamepad", not the correct name.

      Delete
    4. Having looked into it some, it seems like Chrome OS gamepad support has changed quite a bit since I made my original post. The .kl idea came to mind as I'd recently been using keymaps to remap my CB's hardware keyboard in Android, which worked well, but as far as gamepads, it appears that not only are these vendor_product keymap files not being used now, but additionally, there appears to have been quite a bit of work on moving over to a new system of gamepad support in the past year, which seems to be ongoing.

      As a result of the changeover, the calibration/remapping via joydev per my original post appears to no longer work, so my post needs updating.

      ***

      All the stuff at
      https://cs.chromium.org/chromium/src/ui/events/ozone/gamepad/
      seems new. To quote a bit from there:

      "There is a group of gamepads designed for joydev interface but will require a mapping for evdev. There was no existing mapping in current code base but if we move on to use evdev gamepad in arc++, user will notice their gamepad does not work anymore.

      The purpose of this patch is to generate a mapping as close to joydev as possible while being compatible with xbox and adt gamnepads.

      When gamepad vendor id and product id are unknown, we will use generic
      mapping. A static generic mapping can only be compatible with one class
      of gamepad. This commit implements a dynamic generic gamepad mapping. It
      is both compatible with XInput style and ADT style gamepad. And it trys
      it best to be compatible with joydev compatible gamepad."


      "This patch add gamepad event and gamepad support to Ozone.
      gamepad_event_converter_evdev will map evdev gamepad events to w3c
      standard gamepad events. Then the events will be dispatched."

      ***

      I noticed in the old code at

      https://cs.chromium.org/chromium/src/device/gamepad/gamepad_standard_mappings_linux.cc?l=544

      there's only one reference to a Nvidia controller, Vendor 0955", product "7210". I guess that's not your exact one...

      Oh, and yeah, sorry, lsusb wouldn't show devices connected via bluetooth. I guess cat /proc/bus/input/devices may do?.

      So anyway, yeah, it looks like unfortunately not only the .kl approach won't work but also the jscal approach, as joydev is being deprecated.

      Another way of key remapping, that works in theory but seemed unreliable when I tried something similar lately is to use python-evdev. Not sure why using python-evdev was unreliable unfortunately, maybe have been some race condition or something blocking, possibly fixable. I was hoping to do some troubleshooting on this when I got a chance. Making the script for python-evdev is somewhat more involved, though (and this method may also stop working through the container).

      If anything else comes to mind, I'll post back.

      Delete
    5. Bad news, but you were very insightful, thank you!

      Delete
    6. No, that's not the right controller, mine is Vendor 0955", product "7214".
      I'll share some data that I collected, maybe it's of some help. I used html5gampad.com to troubleshoot the buttons.

      First up is Chrome OS:
      http://i1201.photobucket.com/albums/bb348/piendem/html5gamepad_chromeos_zpshlu6kv7l.png
      What's interesting, that vendor and product IDs and even the firmware version are recognized correctly. Triggers are recognized as Axis 3,4, and 7. It's the same on bluetooth and USB.

      Next is Android with bluetooth:
      http://i1201.photobucket.com/albums/bb348/piendem/html5gamepad_android_zpsptoustzm.png
      As to be expected it works correctly. Only four axis and the triggers are recognized as buttons.

      Finally windows with USB:
      http://i1201.photobucket.com/albums/bb348/piendem/html5gamepad_windows_zps8c5hfsyq.png
      Recognized as an Xbox 360 controller, but works as it should.

      I also used the Game Controller Keymapper android app to see how android apps recognize the controller:
      http://i1201.photobucket.com/albums/bb348/piendem/IMG_20180401_091208_zpsmfjjbc3g.jpg
      http://i1201.photobucket.com/albums/bb348/piendem/2b4eb1a2-aa01-4644-8703-f1b6cfae0574_zpsnelmc0f3.jpg
      Recognized as "ChromeOS Gamepad". It's hard to see in the picture, but the right stick moves to the right on both triggers. But they don't just show up as AXIS_Z, but also as L/R trigger, like they should.

      Delete
    7. Yeah, product 7214 is in the newer OS code, at

      https://cs.chromium.org/chromium/src/ui/events/ozone/gamepad/static_gamepad_mapping.cc?rcl=d7843a4769214114c1fa55069976918b6d751b54&l=485

      It looks like they still have a few bugs/tweaks to iron out in the new implementation. I've seen a couple of other posts here and there like yours, where people are still having issues with wrong mappings. If you haven't already done so, I might suggest sending in an issue report (with Alt+Shift+I) with a brief precis of the issue.

      I noticed that it's possible to forward the uinput character device into the Android container, by adding appropriate details into /opt/google/containers/android/config.json. Some Android keyboard mapping apps require uinput to create virtual input devices.I haven't looked into it any further though, and such an approach would be rather involved, and may not be worth messing around with. Just posting the info anyway, in case you (or anyone) might be curious about (the lack of) uinput in Android.

      I'll have a look into evdev when I get a chance, and if I come up with anything on that side of things, I'll post back.

      Delete
    8. Thanks! I already reported this problem, but I though there is a way I could fix this myself.

      Delete