Some time ago, I snatched the awesome game Monaco on Humble Bundle. All the reviews I read were praising the co-op option, and because I had another eager player at home, I decided to check it out.
Problems
The game offers a few modes of multiplayer: online and offline. It would sound perfect, until you realize that both had dealbreaking problems for my purpose.
Online gaming requires a Steam version of the game. Unfortunately, none of us had a Steam account, and even then I'm not paying for the game again.
Offline multiplayer works fine with the Humble version, however, the game designers designed to only allow 1 player to use a keyboard, and others need to use gamepads :(. That means - no '00s style multi-palm keyboard action for you, and if you bring your own keyboard, you're also out of luck.
Options
Well, of course, I could buy an Xbox 360 pad. Realistically, though, I'm not going to do that for a single game, and to have more junk that I need to carry when I move.
The game itself is not open source, so I can't fix it properly. Crossed out.
Monaco input is built on top of the SDL library - awesome! I could write a LD_PRELOAD wrapper to interpret keypresses as pad events.
The xboxdrv solution
I decided to go for the fourth option: play with the Linux input subsystem itself. SDL would probably be easier, but this way it's more fun!
There's a project called xboxdrv, which manipulates input from various game controllers to make it look like something else. With some effort I could surely make it pretend thay my keyboard is an Xbox gamepad!
Work
xboxdrv does marvels when it comes to turning gamepad input into keypresses, or making a gamepad pretend it's something else. However, in the upstream version, it doesn't support turning keyboard input into gamepad events. That I had to do myself.
Patch
I created a patch to xboxdrv, which fixes the inability to understand keyboard input. If you compile that version of xboxdrv, you will be able to specify what key on the keyboard should correspond to what event on a gamepad, using special syntax I introduced.
My monaco.cfg
file for Monaco specified that some keys should be mapped to gamepad events:
[xboxdrv]
[evdev-keymap]
KEY_E=BTN_A
KEY_ENTER=BTN_start
KEY_Q=BTN_Y
KEY_RIGHTSHIFT=BTN_X
KEY_SPACE=BTN_B
KEY_A=BTN_dl # buttons
KEY_w=BTN_du
KEY_SN=BTN_dd
KEY_d=BTN_dr
KEY_UP=ABS_Y1:10000:0 # axis values - axis:pressed value:released value
KEY_DOWN=ABS_Y1:-10000:0
KEY_RIGHT=ABS_X1:10000:0
KEY_LEFT=ABS_X1:-10000:0
#KEY_DOT=ABS_X1:-10000:0+ABS_Y1:10000:0 # you can specify both axes to be triggered when you press a key
The default button mappings might help you a bit.
Using it
To use it, you have to let xboxdrv receive input from your keyboard. First, you must find out which /dev/input/event
entry your keyboard actually is:
$ cat /proc/bus/input/devices | grep -C 10 keyboard
I: Bus=0011 Vendor=0001 Product=0001 Version=ab54
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input3
U: Uniq=
H: Handlers=sysrq kbd event3
B: PROP=0
B: EV=120013
B: KEY=402000000 3803078f800d001 feffffdfffefffff fffffffffffffffe
B: MSC=10
B: LED=7
The interesting line is:
H: Handlers=sysrq kbd event3
That means our keyboard is /dev/input/event3
. Now we can let xboxdrv use it.
$ sudo xboxdrv --evdev /dev/input/event3 --evdev-no-grab --config monaco.cfg
$ ./Monaco/Monaco.bin.x86
Important: remember to add --evdev-no-grab
or you won't be able to use your keyboard as a keyboard again.
Result
The patch is still awaiting review as of the end of 2014, so don't count on this to work out of the box anytime soon.
There are slight problems with the axes support. If you decide that two keys should trigger the same axis (say, left-right), releasing one will reset axis to 0, even if you hold the other. It's not an easily solved problem right now.
In the end, before I finished the patch, we already finished the game by taking turns :P.
How does it all work?
The Linux input system is separated into 3 layers: the input devices (producers), the input drivers and the applications (consumers).
Normally, the producers pass their keypresses, button and axis events to the input drivers, where applications can request to be fed data.
The fancy thing in this setup is that you can write a program to create a virtual input device. This is a technique some people are using to create virtual keyboards, for example programs interpreting speech or screen keyboards.
xboxdrv takes advantage of that, to pretend that it is a new keyboard, or a new controller, and to send the correct input events as a producer. But how does it know what events to send? Well, it registers to the same input system as a consumer, meaning, it can read all input from a device of your choice.
In our case, xboxdrv takes input from the keyboard event device (evdev) and translates it to the input events of a virtual Xbox controller that it registered. More commonly, it is used to create a keyboard and translate controller events to keypresses.
The possibilities!
The input system is flexible enough that you can create whatever virtual device you want and you can take input from any source you wish. There's an interesting program called uinput-mapper, which can save keystrokes to a file and play them back again.
Network
One of the fancy things it can do is send data over a network. This way, you can redirect a keyboard from any Linux machine to any other.
I had to figure out what was the keyboard on my device again. Then, on the host machine, I did something like this:
$ su # or change permissions to /dev/uinput
$ ssh root@N900 /path-to-uinput-mapper/input-read /dev/input7 -G -D | ./uinput-mapper/input-create
The input-read
program takes over the event device, and input-create
reads the output to a new virtual device of the same kind. Then, it creates a new device - another /dev/input/eventX
. The ssh
program makes sure the transmission is safe.
Chaining
You might have noticed, that the device that came out of input-create
is the same kind that xboxdrv accepts. In fact, it's possible to chain them, so that a device somewhere over the network pretends to be an Xbox controller. In fact, I did just that: while testing xboxdrv, I played Monaco using my Nokia N900.
After you started uinput-mapper, you only need the config file and a slightly modified xboxdrv command (replace /dev/input/eventX
with the actual device that input-create
made):
$ sudo xboxdrv --evdev /dev/input/eventX --config monaco.cfg
The difference here is in grabbing. Grabbing is called when an application takes over a device exclusively. In case of xboxdrv, the keyboard will stop sending regular keys and you may get locked out of your machine (pro tip: if this happens, kill xboxdrv if you can).
When we're giving xboxdrv our sole keyboard, that's not the best idea. But if we got an extra keyboard from another device while keeping another for emergency - then it doesn't matter.