Friday, February 21, 2014

Xbox One controller in Chrome on OSX

I've been working lately on better Gamepad API support in Chrome (call it my "20% project"). It's been a fun change of pace from WebGL, primarily because it's much more straightforward code. In the process of learning the ropes, I decided to try something a little crazy: I noticed that our code path for supporting the 360 controller on OSX didn't rely on any drivers, but instead used IOKit to parse the data packets manually. (Check out that code here.) So I figured, hey! Why not try the same thing with the Xbox One controller, ignoring the fact that it's currently supported on exactly zero non-console platforms.

Somewhat unexpectedly, it really wasn't too hard!


Allow me to preface the rest of the post by stressing that I have absolutely no idea what I'm doing. This is entirely a result of me futzing around with APIs that I only learned about 3 days ago, and dumping a lot of hex out to the screen to see what happened when I poked a button. I'm probably doing something laughably wrong, but I could care less. This is fun! :)

The only real "trick" involved here is that the controller is basically non-responsive until you send it some magic bytes. In my current implementation I'm sending  (0x05, 0x20) which I gleaned from Kyle Lemon's github repo. This lights up the "Xbox" logo on the front and the controller starts reporting sensible values. It seems like other sets of bytes work for this as well, not sure what the common thread is.

From there the buttons and axes are all reported in one tightly packed 18 byte blob, which is pretty easy to parse. It seems each data packet has a 4 byte header, the first byte of which tells you the type of data it contains. For buttons the type is 0x20, and the structure looks like this (once you strip off the header):

struct XboxOneButtonData {
  // Buttons
  bool sync   : 1;
  bool dummy1 : 1;  // Always 0.
  bool start  : 1;  // Not entirely sure what these are
  bool back   : 1;  // called on the new controller
  bool a : 1;
  bool b : 1;
  bool x : 1;
  bool y : 1;
  bool dpad_up    : 1;
  bool dpad_down  : 1;
  bool dpad_left  : 1;
  bool dpad_right : 1;
  bool bumper_left       : 1;
  bool bumper_right      : 1;
  bool stick_left_click  : 1;
  bool stick_right_click : 1;
  // Axes
  uint16 trigger_left;
  uint16 trigger_right;
  int16 stick_left_x;
  int16 stick_left_y;
  int16 stick_right_x;
  int16 stick_right_y;

The triggers have a range of 0-1023, and the sticks have a range of -32767 to 32768 (the full int16 range). There's a separate packet type (0x07) that gets sent for when you push the guide button, so that's easily detectable but I'm not currently monitoring it in the Chrome code. The only other packet type I've observed is some sort of heartbeat (0x03), but I'm not sure what purpose it serves.

Beyond that I haven't explored how anything else works. I don't know how to trigger the vibration motors, for example, but in this context I don't care because the gamepad API doesn't have an interface for it anyway. The one big thing I'm missing, and the main blocker before I would consider releasing this, is that you have to somehow explicitly tell the controller to shut down (turn off the LED) when you are done and I don't know how to do it. This means that the controller stays alive until you unplug the USB cable once you've visited a gamepad-enabled page. If anyone happens to know more in this regard I wouldn't mind a hint, and until then I'll poke around myself and see what I can find.

[UPDATE: After talking with some people online it seems like the controller staying on until disconnected might just be how it's designed to work. Game consoles do have different usage patterns than PCs, after all. As a result, I've finished cleaning up the code a bit and submitted the Chrome patch.

In terms of platform support, I'm only really looking at OSX right now. I'll leave it up to Microsoft to support their own controller. (It sounds like they'll eventually make it XInput compatible later this year.) I'm also not too concerned about the Linux side of things, since once a more complete picture of the protocol is available support will inevitably make it's way into the little OS that could.

[UPDATE #2: Microsoft now has official drivers for using the Xbox One controller on Windows]

Anyway, that's been my little side project for the last couple of days. Thought it would be interesting to share with everyone!