Using a "Kraun" low-end USB gamepad under Linux The Kraun gamepad has 2 axes and 10 buttons. Using Fedora 13, _almost_ everyhing works out of the box. Almost, because the two movement axes are recognized as Axis 3 and Axis 4, and most games cannot remap these. What can we do? 1. The `joystick' package The joystick package contains some utilities like jscal and jstest. Using jstest you can check what each of your gamepad's buttons does, which is a first step - for the Kraun gamepad, the buttons were OK, and pressing the movement keys Axes 3 and 4 changed from 0 to -32767 or 32767, depending on the direction. Then there is the jscal program, or its GUI version, jstest-gtk: http://pingus.seul.org/~grumbel/jstest-gtk/ It can calibrate joysticks, which is of not much use here, but jstest-gtk and a non-official version of jscal can also remap axes. The remapping takes place at the driver level, so every program that uses the `joydev' driver should see the change. Since jscal is a command line utility, you can call it automatically when a gamepad is plugged in. 2. Hacking the joydev driver Well, it just didn't work for me, so I've decided to hack the driver. It turned out to be really simple, just download the kernel source, and find drivers/input/joydev.c, and create a Makefile for kernel module compilation, like this: --- Makefile START --- obj-m += joydev.o all: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules clean: make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean --- Makefile END --- You should do a `make' at this time to see if everything works (you will need the kernel headers to compile, of course). Now we can try to take care of the axes... axis events are generated in the `joydev_event' function (EV_ABS case). The `code' variable is the value sent by the gamepad, ie. 3 or 4 in our case, which is remapped by the `absmap' map. The simplest hack is to say that: if(code == 3) event.number = 0; if(code == 4) event.number = 1; Then compile again, and put the joydev.ko file in /lib/modules/$(uname -r)/kernel/driver/input/ (be sure to backup the old one, just in case). Then do a `modprobe -r joydev' as root, and plug in your gamepad. If you run jstest now, it should show axes 0 and 1 as expected. 3. Hacking the SDL library Unfortunately, only a few games use the joydev driver (like PCSX). Most games use the SDL library, so we need to hack that as well. Download the sources from the homepage (libsdl.org), extract, configure and compile. If everything went OK, you should have your libSDL.so.* library in the build/.libs directory. Joystick (and gamepad) handling is done by src/joystick/linux/SDL_sysjoystick.c, and joystick axis event generation is done by the SDL_PrivateJoystickAxis function. It turns out that this is called as much as three times, we need the one that looks like SDL_PrivateJoystickAxis(joystick, joystick->hwdata->abs_map[code], events[i].value); We have another problem here - the gamepad gives values 0, 127 and 255 for the axes, but joysticks are supposed to give negative and positive values in the [-32767, 32767] range, so we change the value part as well: SDL_PrivateJoystickAxis(joystick, (code == 3 ? 0 : (code == 4 ? 1 : joystick->hwdata->abs_map[code])), (events[i].value == 127 ? 0 : (events[i].value < 127 ? -32767 : 32767))); Of course, you can do something better, but this will do for now. Compile. If you want your game to use this modified SDL library, you have three options: 1) Replace the SDL library in /usr/lib/ (not recommended) 2) Put a copy (or symbolic link) of the new library in the game's directory 3) Run the game in a modified environment, for example for zsnes: env LD_LIBRARY_PATH=/path/to/modified/SDL/library:$LD_LIBRARY_PATH zsnes Works like a miracle. I've also written a small SDL test program, that writes the number and type of gamepads (joysticks) and then writes every event of the first gamepad until you press ESC. // compile with: g++ -o gamepad-test gamepad-test.cc -lSDL #include #include #include int main(int argc, char *argv[]) { if(SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0) { std::cerr << "Initialization failed." << std::endl; return 1; } if(!SDL_SetVideoMode(320, 200, 0, 0)) { std::cerr << "Could not set video mode: " << SDL_GetError() << std::endl; SDL_Quit(); return 1; } std::cout << "Press ESC to quit." << std::endl; int n = SDL_NumJoysticks(); std::cout << "# of gamepads: " << n << std::endl; for(int i = 0; i < n; ++i) std::cout << SDL_JoystickName(i) << std::endl; SDL_Joystick *joystick; SDL_JoystickEventState(SDL_ENABLE); joystick = SDL_JoystickOpen(0); std::string dir; bool quit = false; while(!quit) { SDL_Event event; while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_KEYDOWN: if(event.key.keysym.sym == SDLK_ESCAPE) quit = true; break; case SDL_JOYAXISMOTION: if(event.jaxis.axis == 0) { if(event.jaxis.value < 0) dir = "left"; else if(event.jaxis.value > 0) dir = "right"; else dir = "center"; } else if(event.jaxis.axis == 1) { if(event.jaxis.value < 0) dir = "up"; else if(event.jaxis.value > 0) dir = "down"; else dir = "center"; } else { if(event.jaxis.value < 0) dir = "some axis -"; else if(event.jaxis.value > 0) dir = "some axis +"; else dir = "some axis 0"; } std::cout << dir << std::endl; break; case SDL_JOYBUTTONDOWN: std::cout << "button " << (int)event.jbutton.button << std::endl; break; } } } SDL_JoystickClose(joystick); SDL_Quit(); return 0; }