Saturday, January 3, 2009

uinput jni interface

I needed to build a Java interface to the uinput kernel device, and as this was my first non-trivial jni code, I'll document it here. (This ties in with the wiigee library)

Note this requires the uipnut kernel mode to be loaded, and the running user to have write access to the uinput device.

First, write the Java code containing the native library references:

public class X11Input {
//Singleton
private static X11Input u;
public static X11Input getInstance() {
if(u == null) {
u = new X11Input();
}
return u;
}

//native methods
//private - called to destroy, and init native interface
private native void destroy();
private native void init();
//public functions
public native void mouseMove(int x, int y);
public native void keyPress(int key);
public native void keyRelease(int key);

//Load native library
static {
System.loadLibrary("X11Input");
}

//We add the Shutdown hook, and init the uinput interface in the contructor
public X11Input() {
init();
Runtime.getRuntime().addShutdownHook(new Thread() {
public void run() {
X11Input.getInstance().destroy();
System.out.println("Destroying the X11Input singleton jni");
}
});
}
}
Note the use of the addShutdownHook to ensure* the uinput device is correctly closed.

Next, using the compiled class and javah, generate a c header file:
javah -jni org.wiiboard.x11.X11Input

This is executed at the root of the bin directory, and will generate the file:
org_wiiboard_x11_X11Input.h

Next, write the c code that adds the workings behind the the functions:
#include
#include
#include
#include
#include
#include
#include
#include
#include

#include
#include
#include "UinputTest.h"

/* Globals */
static int uinp_fd = -1;
struct uinput_user_dev uinp; // uInput device structure
struct input_event event; // Input device structure
/* Setup the uinput device */
int setup_uinput_device()
{
// Temporary variable
int i=0;
// Open the input device
uinp_fd = open("/dev/input/uinput", O_WRONLY | O_NDELAY);
if (uinp_fd == 0)
{
printf("Unable to open /dev/input/uinput\n");
return -1;
}
memset(&uinp,0,sizeof(uinp)); // Intialize the uInput device to NULL
strncpy(uinp.name, "PolyVision Touch Screen", UINPUT_MAX_NAME_SIZE);
uinp.id.version = 4;
uinp.id.bustype = BUS_USB;
// Setup the uinput device
ioctl(uinp_fd, UI_SET_EVBIT, EV_KEY);
ioctl(uinp_fd, UI_SET_EVBIT, EV_REP);
ioctl(uinp_fd, UI_SET_EVBIT, EV_REL);
ioctl(uinp_fd, UI_SET_RELBIT, REL_X);
ioctl(uinp_fd, UI_SET_RELBIT, REL_Y);
for (i=0; i <> 0 && ( x != 0 || y != 0 )) {

if(x != 0) {
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_REL;
event.code = REL_X;
event.value = x;
write(uinp_fd, &event, sizeof(event));
}
if(y!=0) {
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_REL;
event.code = REL_Y;
event.value = y;
write(uinp_fd, &event, sizeof(event));
}

memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(uinp_fd, &event, sizeof(event));
}
}

JNIEXPORT void JNICALL
Java_org_wiiboard_x11_X11Input_keyPress(JNIEnv *env, jobject obj, jint key)
{
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = key;
event.value = 1;
write(uinp_fd, &event, sizeof(event));

memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(uinp_fd, &event, sizeof(event));
}

JNIEXPORT void JNICALL
Java_org_wiiboard_x11_X11Input_keyRelease(JNIEnv *env, jobject obj, jint key)
{
memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_KEY;
event.code = key;
event.value = 0;
write(uinp_fd, &event, sizeof(event));

memset(&event, 0, sizeof(event));
gettimeofday(&event.time, NULL);
event.type = EV_SYN;
event.code = SYN_REPORT;
event.value = 0;
write(uinp_fd, &event, sizeof(event));
}

JNIEXPORT void JNICALL
Java_org_wiiboard_x11_X11Input_init(JNIEnv *env, jobject obj)
{
setup_uinput_device();
}

JNIEXPORT void JNICALL
Java_org_wiiboard_x11_X11Input_destroy(JNIEnv *env, jobject obj)
{
destroy_uinput_device();
}

Now compile:
gcc --share -o libX11Input.so -I/include/ -I/include/linux org_wiiboard_x11_X11Input.c /jre/lib/i386/server/libjvm.so

That will generate the libX11Input.so library.

Now you can run the java code with the java.lirary.path pointing to your new .so file's directory:
java -Djava.library.path=. X11InputTest

Wiigee on linux

Wiigee is a wiimote library written in Java that can learn and identify gestures. A demo application is available for download, and source is included in the jar file.

I have used wiigee under linux (ubuntu i386 and Kubuntu x64), and this is what I had to do to get it running.

First, to get it running, download the the bluecove libraries (I used version 2.1.0), and maven2.
Make sure libbluetooth and libbluetooth-dev are installed (and libstdc++.so.6 is linked too libstdc++.so on Kubuntu)

The bluecove MicroeditionConnector.java needs to have the L2CAP validation removed (apparently the wii mote uses non-standard ports)

export the JAVA_HOME variable (if its not alerady set), and execute:
mvn -Dmaven.test.skip=true -e
That will generate all the bluetooth libraries

Make the bluecove, and bluecove-gpl jars, and the libbluecove native binary lib avaiable to the wiigee library, and you're set.