Just some stuff about front-end development


Node.js gamepad driver

Node.js game pad in action

Some time ago I wanted to play an old-school game and I wanted to use my game pad, and of course I could not find it. The solution? Create my own game pad, but with limited hardware related skills that would be quite difficult. The next best thing - to use a touch capable device. But it turned out quite quickly that it would not be so easy. It’s not a problem when the HTML5 game pad controls an HTML5 game on the same server/browser, but what about native games? A driver would be needed for that and my level of expertise in that area was the same as the level in hardware building mumbo-jumbo. My experimental “driver” had two main goals: to run on Ubuntu and be build in Node.js

The following Post is not a tutorial, so I’m not covering the subject from top to bottom, but I’m providing, a great starting point. This should give you some idea how things work and what to expect from such a device. At the end I’m linking to a Node.js application which acts like a device driver. This app is not in any way a production ready solution, but only an experiment, so keep in mind that there are many bugs, and that there is a high possibility that it will not run on your system (I’ve created it and tested only on Ubuntu 13.10)

First idea

First thing I’ve tried was to read some mouse input from the /dev/input/event* device (because reading is always easier when you heave no idea what to write). Those devices from which we can read (and write) are located in /dev/input directory and are pointing to various (mostly) physical devices.

/dev/input listing

First thing: a way to access the data stream from the mouse, on my system it was the /dev/input/event5 device. In Node.js I’ve could just open and read from the device by using the fs.open and fs.read functions and repeat the process in a loop for a constant data retrieval from the device. But there is a much simpler approach - streams. In the most basic and spartan form of the reading the script would look like this:

require('fs').createReadStream('/dev/input/event5').on('data', function( buffer ){ console.log( buffer ) });

The buffer available for the callback contains the following data:

  • tv_sec and tv_usec - time of the event in seconds and microseconds
  • type - there are various types of events, for example events related to a change of absolute or relative position or a button press.
  • code - for example: for which axis this event was called.
  • value - can be the position on an axis or id of a button

For a mouse those values probably will be set as follows:

  • type: 2 (EV_REL) for relative position
  • code: 0 (REL_X) and 1 (REL_Y) for horizontal and vertical axis
  • value: from -N to +N for the difference of previous position on a given axis

For a joystick:

  • type: 3 (EV_ABS) for the absolute position
  • code: 0 (REL_X) and 1 (REL_Y) for horizontal and vertical axis
  • value: change in the position from -N to +N

Those properties can be obtained by using Buffer methods, as displayed below:

var tv_sec   = buffer.readInt32LE(0),
    tv_usec  = buffer.readInt32LE(8),
    type     = buffer.readUInt16LE(16),
    code     = buffer.readUInt16LE(18),
    value    = buffer.readInt32LE(20);

More info about input events and their structure (and types of those fields) can be found under the following documentation: https://www.kernel.org/doc/Documentation/input/input.txt

Now, when we know the structure of the data, we can start to write some stuff to this device, because the structure for writing is exactly the same.

The script that I've used to read the device data can be downloaded here.

/dev/input/event5 output

Writing to the input device

What a surprise, I’ve found my game pad (actually, I’ve asked my girlfriend where it is, whenever something goes missing, she knows where it is… not that it is suspicious or anything). Of course I could stop my research right now and just play the game, but no, the show must go on. So I’ve plugged the joystick in and got the same structure as with the mouse but with different values.

Creating a device

First, we need to open /dev/uinput for reading and writing.

var fd = fs.openSync( '/dev/uinput', 'w+');

Then, we need to write some basic info about the device we want to create.

  • name - name of the device which can be up to 80 characters long
  • id - which contains following the IDs and numbers
  • bustype
  • vendor
  • product
  • version
var name    = "Custom device"; // max 80 chars
var bustype = 0x3; // USB
var vendor  = 0x1337; // Just some random numbers
var product = 0x1010;
var version = 1;

var buff = new Buffer(1116);
buff.write(name, 0, 80);
buff.writeUInt16LE( bustype, 80 );
buff.writeUInt16LE( vendor , 82 );
buff.writeUInt16LE( product, 84 );
buff.writeUInt16LE( version, 86 );

fs.writeSync( fd, buff, 0, buff.length, null);

But that’s not all. We also need to specify features which our device will support. We can do it by using ioctl (input/output control). By default, Node.js doesn’t support ioctl out of the box. But of course there is a module for that. I’ve used a simplified version of https://github.com/bramp/node-ioctl for this job.

For my joystick I’ve used the following setting. Values for that data can be found in the documentation to which I’ve linked earlier or in the uinput.js file.

[[ 'UI_SET_EVBIT' , 'EV_KEY'    ],
 [ 'UI_SET_EVBIT' , 'EV_REL'    ],
 [ 'UI_SET_RELBIT', 'REL_X'     ],
 [ 'UI_SET_RELBIT', 'REL_Y'     ],
 [ 'UI_SET_RELBIT', 'REL_WHEEL' ]].forEach(function( data ){
     ioctl.ioctl( fd, uinput[ data[0] ], uinput[ data[1] ]);

The last thing to do is to actually create the device by calling:

ioctl.ioctl(dev_fd, uinput.UI_DEV_CREATE, 0 );

Writing to a device

Writing is the easiest part of the whole process. Like I’ve mentioned before, the structure of the data that we are writing is identical with the one we had read from the device before.

var ev = new Buffer(24);

var tv_sec   = Math.round( Date.now() / 1000 ),
    tv_usec  = Math.round( Date.now() % 1000 * 1000 ),
    type     = 0x03, // EV_ABS,
    code     = 0x00, // ABS_X,
    value    = -32767;

ev.writeInt32LE(tv_sec, 0);
ev.writeInt32LE(tv_usec, 8);
ev.writeInt16LE(type, 16);
ev.writeInt16LE(code, 18);
ev.writeInt32LE(value, 20);

var ev_end = new Buffer(24);

ev_end.writeInt32LE(tv_sec, 0);
ev_end.writeInt32LE(tv_usec, 8);

fs.writeSync( fd, ev, 0, ev.length, null );
fs.writeSync( fd, ev_end, 0, ev_end.length, null );

Destroying device

var UI_DEV_DESTROY = 21762;
    ioctl.ioctl( dev_fd, UI_DEV_DESTROY, 0);

Front-end client

For the client I’ve prepared a small and dirty application for game pad like input controls. It has a small bonus of being able to emulate a touch pad on touch devices.

Photo of the front-end client simulating a game pad

The whole application works almost perfectly on a touch screen device - the catch? Without physical feedback from the device, it is quite hard in the beginning to play a game with XYAB buttons (thou the stick is fine, it’s dynamic so the user won’t loose it). Those buttons cannot be moved like the joystick, because there are four of them, and it would be impossible to determine which button the player wanted to press.


All the files can be downloaded under the WTFPL License here.

comments powered by Disqus