Java – Global (low level) Keyboard / Mouse Hook

by Kristian Kraljic

I’d like to start my small blog with a neat little Java snippet. As I wanted to have a global key/mouse hook for my Java programs, I found a great little script using the JNI at Jotschi’s Blog. Unfortuately Jotschi doesn’t develop on any windows binaries anymore, so he can not provide enhancements. Therefore I took Jotschi’s code, fixed some bugs and enhanced especially the Java part. Here is what I did:

  • Compiled and testet the native libaries on Windows Vista & 7 (x86 and x64, 32 & 64 bit JVM)
  • Split the keyboard / mouse hook into two different classes and native libraries, so they can be used separately
  • Refactored the code in general
    • Added KeyEvent & KeyListener, MouseEvent & MouseListener classes. So the overall concept is now more likely to the standard Java AWT keyboard & mouse events
    • Fixed some bugs, especially time consuming processes in the event handlers kept crashing the JNI (added a buffer mechansim)
    • Added support for all keyboard events (such as key-up and also the control keys [ALT, CTRL and SHIFT] are now easier to access)
    • Added support for all low level mouse events (such as mouse buttons)
    • It is now guranteed that the mouse coordinate is inside the screen bounds (before -1 or 1026 have been valid values at a 1024×800 pixel resolution)
  • The hook’s are now non-blocking, which means, as the last (non-deamon) thread ends, also the hook-threads end
  • The native libraries are now bundled into the jar files. So it is possible, but not required to specify them as a virtual machine parameter
  • Last but not least, I will try to enhance the Global Keyboard / Mouse Hook, if you have any ideas / problems with it. Feel free to write it into the comments below!

The general description: Global Keyboard / Mouse Hook for Java applications. Normally keyboard and mouse listeners in Java only work, if the registered component has the focus. If, for example, any window looses it’s focus (minimized) it isn’t possible to track any keyboard / mouse events anymore. Therefore we will have to use the JNI (Java Nativ Interface), to register a low level keyboard / mouse hook to the system. This is done using a native library (compiled on Windows 7 x64 using 32 & 64 bit JVM).

Download Source / Binaries: (Distributed under the Simplified BSD License)
KeyboardHook (Version 0.3)

MouseHook (Version 0.3)

Here is the code for the keyboard part:

#include <jni.h>

/* Header for class KeyboardHook */
#ifndef _Included_KeyboardHook
#define _Included_KeyboardHook
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     KeyboardHook
 * Method:    registerHook
 * Signature: (LGlobalEventListener;)V
 */
JNIEXPORT void JNICALL Java_de_ksquared_system_keyboard_KeyboardHook_registerHook(JNIEnv *,jobject thisObj,jobject listenerObj);
/*
 * Class:     KeyboardHook
 * Method:    unregisterHook
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_de_ksquared_system_keyboard_KeyboardHook_unregisterHook(JNIEnv *env,jobject thisObj);

#ifdef __cplusplus
}
#endif
#endif
#include <windows.h>
#include <jni.h>
#include "KeyboardHook.h"

#ifdef DEBUG
#define DEBUG_PRINT(x) printf x
#else
#define DEBUG_PRINT(x) do {} while (0)
#endif 

HINSTANCE hInst = NULL;

JavaVM * jvm = NULL;
DWORD hookThreadId = 0;

jobject keyboardHookObject = NULL;
jobject globalKeyListenerObject = NULL;
jmethodID processKeyMethod = NULL;

extern "C"
BOOL APIENTRY DllMain(HINSTANCE _hInst,DWORD reason,LPVOID reserved)  {
	switch(reason) {
		case DLL_PROCESS_ATTACH:
			DEBUG_PRINT(("NATIVE: DllMain - DLL_PROCESS_ATTACH.n"));
			hInst = _hInst;
			break;
		default:
			break;
	}
	return TRUE;
}

LRESULT CALLBACK LowLevelKeyboardProc(int nCode,WPARAM wParam,LPARAM lParam)  {
	JNIEnv* env;
	KBDLLHOOKSTRUCT* p = (KBDLLHOOKSTRUCT *)lParam;
	if(jvm->AttachCurrentThread((void **)&env, NULL)>=0) {
		jboolean transitionState = (jboolean)FALSE;
		switch(wParam)  {
			case WM_KEYDOWN: case WM_SYSKEYDOWN:
				transitionState = (jboolean)TRUE;
			case WM_KEYUP: case WM_SYSKEYUP:
				env->CallVoidMethod(keyboardHookObject,processKeyMethod,transitionState,p->vkCode,globalKeyListenerObject);
				break;
			default:
				break;
		}
	}	else DEBUG_PRINT(("NATIVE: LowLevelKeyboardProc - Error on the attach current thread.n"));
	return CallNextHookEx(NULL,nCode,wParam,lParam);
}

JNIEXPORT void JNICALL Java_de_ksquared_system_keyboard_KeyboardHook_registerHook(JNIEnv * env,jobject obj,jobject _globalKeyListenerObject) {
	DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_keyboard_KeyboardHook_registerHook - Hooking started!n"));
	
	HHOOK hookHandle = SetWindowsHookEx(WH_KEYBOARD_LL,LowLevelKeyboardProc,hInst,0);
	globalKeyListenerObject = _globalKeyListenerObject;
	
	if(hookHandle==NULL) {
		DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_keyboard_KeyboardHook_registerHook - Hook failed!n"));
		return;
	} else DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_keyboard_KeyboardHook_registerHook - Hook successfuln"));
	
	keyboardHookObject = env->NewGlobalRef(obj);
	jclass cls = env->GetObjectClass(keyboardHookObject);
	processKeyMethod = env->GetMethodID(cls,"processKey","(ZILde/ksquared/system/keyboard/GlobalKeyListener;)V");
	
	env->GetJavaVM(&jvm);
	hookThreadId = GetCurrentThreadId();
	
	MSG message;
	while(GetMessage(&message,NULL,0,0)) {
		TranslateMessage(&message);
		DispatchMessage(&message);
	}
	
	DEBUG_PRINT(((!UnhookWindowsHookEx(hookHandle))?("NATIVE: Java_de_ksquared_system_keyboard_KeyboardHook_registerHook - Unhook failedn")
	                                                :"NATIVE: Java_de_ksquared_system_keyboard_KeyboardHook_registerHook - Unhook successfuln"));
}

JNIEXPORT void JNICALL Java_de_ksquared_system_keyboard_KeyboardHook_unregisterHook(JNIEnv *env,jobject object) {
	if(hookThreadId==0) return;	
	DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_keyboard_KeyboardHook_unregisterHook - call PostThreadMessage.n"));
	PostThreadMessage(hookThreadId,WM_QUIT,0,0L);
}
package de.ksquared.system.keyboard;

import java.util.List;
import java.util.Vector;

public class GlobalKeyListener {
	protected PoolHook hook;
	public GlobalKeyListener() { (hook=new PoolHook(this)).start();	}
	protected List<KeyListener> listeners = new Vector<KeyListener>();

	public void addKeyListener(KeyListener listener) { listeners.add(listener); }
	public void removeKeyListener(KeyListener listener) { listeners.remove(listener); }

	void keyPressed(KeyEvent event) {
		try {
			for(KeyListener listener:listeners)
				listener.keyPressed(event);
		} catch(Exception e) { e.printStackTrace(); }
	}
	void keyReleased(KeyEvent event) {
		try {
			for(KeyListener listener:listeners)
				listener.keyReleased(event);
		} catch(Exception e) { e.printStackTrace(); }
	}
}
package de.ksquared.system.keyboard;

public class KeyAdapter implements KeyListener {
	@Override public void keyPressed(KeyEvent event) {}
	@Override public void keyReleased(KeyEvent event) {}
}
package de.ksquared.system.keyboard;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

class PoolHook extends Thread {
	private KeyboardHook hook;
	private GlobalKeyListener listener;

	PoolHook(GlobalKeyListener listener) {
		this.setDaemon(true);
		this.listener = listener;
	}

	public void run() {
		hook = new KeyboardHook();
		hook.registerHook(listener);
	}
}

class EventProcedure extends Thread {
	private KeyboardHook hook;
	
	EventProcedure(KeyboardHook hook) {
		this.setDaemon(true);
		this.hook = hook;
	}
	
	@Override public void run() {
		while(true) {
			if(!hook.buffer.isEmpty()) {
				KeyEvent event = hook.buffer.remove(0);
				GlobalKeyListener listener = event.listener;
				if(event.transitionState)
					   listener.keyPressed(event);
				else listener.keyReleased(event);
			} else try { Thread.sleep(10); }	catch(InterruptedException e) { e.printStackTrace(); }
		}
	}
}

class KeyboardHook {	
	private boolean altPressed,shiftPressed,ctrlPressed,extendedKey;
	
	List<KeyEvent> buffer = Collections.synchronizedList(new LinkedList<KeyEvent>());
	private EventProcedure procedure = new EventProcedure(this);
	
	public KeyboardHook() { if(Native.load()) procedure.start(); }
	void processKey(boolean transitionState,int virtualKeyCode,GlobalKeyListener listener) {
		processControlKeys(transitionState,virtualKeyCode);
		buffer.add(new KeyEvent(this,listener,transitionState,virtualKeyCode,altPressed,shiftPressed,ctrlPressed,extendedKey));
	}
	
	native void registerHook(GlobalKeyListener listener);
	native void unregisterHook();
	
	void processControlKeys(boolean transitionState,int virtualKeyCode) {
		switch(virtualKeyCode) {
		case KeyEvent.VK_RWIN: extendedKey = transitionState; break;
		case KeyEvent.VK_RMENU: extendedKey = transitionState;
		case KeyEvent.VK_MENU: case KeyEvent.VK_LMENU:
			altPressed = transitionState;
			break;			
		case KeyEvent.VK_RSHIFT: extendedKey = transitionState;
		case KeyEvent.VK_SHIFT: case KeyEvent.VK_LSHIFT:
			shiftPressed = transitionState;
			break;
		case KeyEvent.VK_RCONTROL: extendedKey = transitionState;
		case KeyEvent.VK_CONTROL: case KeyEvent.VK_LCONTROL:
			ctrlPressed = transitionState;
			break;
		}
	}
}
package de.ksquared.system.keyboard;

import java.util.EventObject;

public class KeyEvent extends EventObject {
	private static final long serialVersionUID = -8194688548489965445L;
	
	public static final int VK_UNDEFINED = 0x0;
	public static final int VK_LBUTTON = 0x01,VK_RBUTTON = 0x02,VK_CANCEL = 0x03,VK_MBUTTON = 0x04,VK_XBUTTON1 = 0x05,VK_XBUTTON2 = 0x06,VK_BACK = 0x08,VK_TAB = 0x09,VK_CLEAR = 0x0C,VK_RETURN = 0x0D,VK_SHIFT = 0x10,VK_CONTROL = 0x11,VK_MENU = 0x12,VK_PAUSE = 0x13,VK_CAPITAL = 0x14,VK_KANA = 0x15,VK_HANGUEL = 0x15,VK_HANGUL = 0x15,VK_JUNJA = 0x17,VK_FINAL = 0x18,VK_HANJA = 0x19,VK_KANJI = 0x19,VK_ESCAPE = 0x1B,VK_CONVERT = 0x1C,VK_NONCONVERT = 0x1D,VK_ACCEPT = 0x1E,VK_MODECHANGE = 0x1F,VK_SPACE = 0x20,VK_PRIOR = 0x21,VK_NEXT = 0x22,VK_END = 0x23,VK_HOME = 0x24,VK_LEFT = 0x25,VK_UP = 0x26,VK_RIGHT = 0x27,VK_DOWN = 0x28,VK_SELECT = 0x29,VK_PRINT = 0x2A,VK_EXECUTE = 0x2B,VK_SNAPSHOT = 0x2C,VK_INSERT = 0x2D,VK_DELETE = 0x2E,VK_HELP = 0x2F,VK_0 = 0x30,VK_1 = 0x31,VK_2 = 0x32,VK_3 = 0x33,VK_4 = 0x34,VK_5 = 0x35,VK_6 = 0x36,VK_7 = 0x37,VK_8 = 0x38,VK_9 = 0x39,VK_A = 0x41,VK_B = 0x42,VK_C = 0x43,VK_D = 0x44,VK_E = 0x45,VK_F = 0x46,VK_G = 0x47,VK_H = 0x48,VK_I = 0x49,VK_J = 0x4A,VK_K = 0x4B,VK_L = 0x4C,VK_M = 0x4D,VK_N = 0x4E,VK_O = 0x4F,VK_P = 0x50,VK_Q = 0x51,VK_R = 0x52,VK_S = 0x53,VK_T = 0x54,VK_U = 0x55,VK_V = 0x56,VK_W = 0x57,VK_X = 0x58,VK_Y = 0x59,VK_Z = 0x5A,VK_LWIN = 0x5B,VK_RWIN = 0x5C,VK_APPS = 0x5D,VK_SLEEP = 0x5F,VK_NUMPAD0 = 0x60,VK_NUMPAD1 = 0x61,VK_NUMPAD2 = 0x62,VK_NUMPAD3 = 0x63,VK_NUMPAD4 = 0x64,VK_NUMPAD5 = 0x65,VK_NUMPAD6 = 0x66,VK_NUMPAD7 = 0x67,VK_NUMPAD8 = 0x68,VK_NUMPAD9 = 0x69,VK_MULTIPLY = 0x6A,VK_ADD = 0x6B,VK_SEPARATOR = 0x6C,VK_SUBTRACT = 0x6D,VK_DECIMAL = 0x6E,VK_DIVIDE = 0x6F,VK_F1 = 0x70,VK_F2 = 0x71,VK_F3 = 0x72,VK_F4 = 0x73,VK_F5 = 0x74,VK_F6 = 0x75,VK_F7 = 0x76,VK_F8 = 0x77,VK_F9 = 0x78,VK_F10 = 0x79,VK_F11 = 0x7A,VK_F12 = 0x7B,VK_F13 = 0x7C,VK_F14 = 0x7D,VK_F15 = 0x7E,VK_F16 = 0x7F,VK_F17 = 0x80,VK_F18 = 0x81,VK_F19 = 0x82,VK_F20 = 0x83,VK_F21 = 0x84,VK_F22 = 0x85,VK_F23 = 0x86,VK_F24 = 0x87,VK_NUMLOCK = 0x90,VK_SCROLL = 0x91,VK_LSHIFT = 0xA0,VK_RSHIFT = 0xA1,VK_LCONTROL = 0xA2,VK_RCONTROL = 0xA3,VK_LMENU = 0xA4,VK_RMENU = 0xA5,VK_BROWSER_BACK = 0xA6,VK_BROWSER_FORWARD = 0xA7,VK_BROWSER_REFRESH = 0xA8,VK_BROWSER_STOP = 0xA9,VK_BROWSER_SEARCH = 0xAA,VK_BROWSER_FAVORITES = 0xAB,VK_BROWSER_HOME = 0xAC,VK_VOLUME_MUTE = 0xAD,VK_VOLUME_DOWN = 0xAE,VK_VOLUME_UP = 0xAF,VK_MEDIA_NEXT_TRACK = 0xB0,VK_MEDIA_PREV_TRACK = 0xB1,VK_MEDIA_STOP = 0xB2,VK_MEDIA_PLAY_PAUSE = 0xB3,VK_LAUNCH_MAIL = 0xB4,VK_LAUNCH_MEDIA_SELECT = 0xB5,VK_LAUNCH_APP1 = 0xB6,VK_LAUNCH_APP2 = 0xB7,VK_OEM_1 = 0xBA,VK_OEM_PLUS = 0xBB,VK_OEM_COMMA = 0xBC,VK_OEM_MINUS = 0xBD,VK_OEM_PERIOD = 0xBE,VK_OEM_2 = 0xBF,VK_OEM_3 = 0xC0,VK_OEM_4 = 0xDB,VK_OEM_5 = 0xDC,VK_OEM_6 = 0xDD,VK_OEM_7 = 0xDE,VK_OEM_8 = 0xDF,VK_OEM_102 = 0xE2,VK_PROCESSKEY = 0xE5,VK_PACKET = 0xE7,VK_ATTN = 0xF6,VK_CRSEL = 0xF7,VK_EXSEL = 0xF8,VK_EREOF = 0xF9,VK_PLAY = 0xFA,VK_ZOOM = 0xFB,VK_NONAME = 0xFC,VK_PA1 = 0xFD,VK_OEM_CLEAR = 0xFE;
	
	protected GlobalKeyListener listener;
	protected boolean transitionState,altPressed,shiftPressed,ctrlPressed,extendedKey;
	protected int virtualKeyCode;

	public KeyEvent(Object source,GlobalKeyListener listener,boolean transitionState,int virtualKeyCode,boolean altPressed,boolean shiftPressed,boolean ctrlPressed,boolean extendedKey) {
		super(source);
		this.listener = listener;
		this.transitionState = transitionState;
		this.virtualKeyCode = virtualKeyCode;
		this.altPressed = altPressed;
		this.shiftPressed = shiftPressed;
		this.ctrlPressed = ctrlPressed;
		this.extendedKey = extendedKey;
	}

	public boolean getTransitionState() { return transitionState; }
	public int getVirtualKeyCode() { return virtualKeyCode; }
	public boolean isAltPressed() { return altPressed; }
	public boolean isShiftPressed() { return shiftPressed; }
	public boolean isCtrlPressed() { return ctrlPressed; }
	public boolean isExtendedKey() { return extendedKey; }
	public boolean equals(KeyEvent event) {
		return event.getVirtualKeyCode()==virtualKeyCode
		     &&event.isExtendedKey()==extendedKey
		     &&event.isAltPressed()==altPressed;
	}
	
	@Override public String toString() {
		StringBuilder string = new StringBuilder().append(virtualKeyCode).append(" [").append(transitionState?"down":"up");
		if(altPressed) string.append(",alt");
		if(shiftPressed) string.append(",shift");
		if(ctrlPressed) string.append(",ctrl");
		if(extendedKey) string.append(",extended");
		return string.append(']').toString();
	}
}
package de.ksquared.system.keyboard;

import java.util.EventListener;

public interface KeyListener extends EventListener {
  public void keyPressed(KeyEvent event);
  public void keyReleased(KeyEvent event);
}
package de.ksquared.system.keyboard;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class Native {
	private static Boolean loaded = null;

	static boolean load() {
		if(loaded!=null)
			return loaded==Boolean.TRUE;

		String libpath = System.getProperty("de.ksquared.system.keyboard.lib.path"),
				   libname = System.getProperty("de.ksquared.system.keyboard.lib.name");
		if(libname==null)
			libname = System.mapLibraryName("keyboardhook");
		try {
			if(libpath==null)
				   System.loadLibrary("keyboardhook");
			else System.load(new File(libpath,libname).getAbsolutePath());
			return (loaded=Boolean.TRUE);
		}	catch(UnsatisfiedLinkError e) { /* do nothing, try next */ }

		String osname = System.getProperty("os.name").toLowerCase(),
				   osarch = System.getProperty("os.arch");
		     if(osname.startsWith("mac os")) { osname = "mac"; osarch = "universal"; }
		else if(osname.startsWith("windows")) osname = "win";
		else if(osname.startsWith("sunos")) osname = "solaris";
		if(osarch.startsWith("i")&&osarch.endsWith("86"))
			osarch = "x86";
		libname = "keyboardhook-"+osname+'-'+osarch+".lib";
		try {
			InputStream input = Native.class.getClassLoader().getResourceAsStream(libname);
			if(input==null)
				throw new Exception("libname: "+libname+" not found");
			File temp = File.createTempFile("keyboardhook-",".lib");
			temp.deleteOnExit();
			OutputStream out = new FileOutputStream(temp);
			byte[] buffer = new byte[1024];
			int read;
			while((read = input.read(buffer))!=-1)
				out.write(buffer,0,read);
			input.close(); out.close();
			System.load(temp.getAbsolutePath());
			return (loaded=Boolean.TRUE);
		} catch(Exception e) { /* do nothing, go on */ }
		return (loaded=Boolean.FALSE);
	}
}

And here is the code for the mouse part:

#include <jni.h>

/* Header for class MouseHook */
#ifndef _Included_MouseHook
#define _Included_MouseHook
#ifdef __cplusplus
extern "C" {
#endif

/*
 * Class:     MouseHook
 * Method:    registerHook
 * Signature: (LGlobalEventListener;)V
 */
JNIEXPORT void JNICALL Java_de_ksquared_system_mouse_MouseHook_registerHook(JNIEnv *,jobject thisObj,jobject listenerObj);
/*
 * Class:     MouseHook
 * Method:    unregisterHook
 * Signature: ()V
 */
JNIEXPORT void JNICALL Java_de_ksquared_system_mouse_MouseHook_unregisterHook(JNIEnv *env,jobject thisObj);

#ifdef __cplusplus
}
#endif
#endif
#include <windows.h>
#include <jni.h>
#include "MouseHook.h"
#define DEBUG 1
#ifdef DEBUG
#define DEBUG_PRINT(x) printf x
#else
#define DEBUG_PRINT(x) do {} while (0)
#endif 

HINSTANCE hInst = NULL;

JavaVM * jvm = NULL;
DWORD hookThreadId = 0;

jobject mouseHookObject = NULL;
jobject globalMouseListenerObject = NULL;
jmethodID processMouseButtonMethod = NULL;
jmethodID processMouseMoveMethod = NULL;

LONG mouseLocationX=-1,mouseLocationY=-1;

extern "C"
BOOL APIENTRY DllMain(HINSTANCE _hInst,DWORD reason,LPVOID reserved)  {
	switch(reason) {
		case DLL_PROCESS_ATTACH:
			DEBUG_PRINT(("NATIVE: DllMain - DLL_PROCESS_ATTACH.n"));
			hInst = _hInst;
			break;
		default:
			break;
	}
	return TRUE;
}

LRESULT CALLBACK LowLevelMouseProc(int nCode,WPARAM wParam,LPARAM lParam) {
	JNIEnv* env;
	if(jvm->AttachCurrentThread((void **)&env, NULL)>=0) {
		if(nCode==HC_ACTION) {
			MOUSEHOOKSTRUCT* pStruct = (MOUSEHOOKSTRUCT*)lParam;
			switch(wParam) {
			case WM_LBUTTONDOWN: case WM_LBUTTONUP:
			case WM_RBUTTONDOWN: case WM_RBUTTONUP:
				env->CallVoidMethod(mouseHookObject,processMouseButtonMethod,(jint)wParam,globalMouseListenerObject);
				break;
			case WM_MOUSEMOVE:
				if(pStruct->pt.x!=mouseLocationX||pStruct->pt.y!=mouseLocationY) {
					env->CallVoidMethod(mouseHookObject,processMouseMoveMethod,(jint)pStruct->pt.x,(jint)pStruct->pt.y,globalMouseListenerObject);
					mouseLocationX = pStruct->pt.x;
					mouseLocationX = pStruct->pt.y;
				}
				break;
			default:
				break;
			}
		}
	} else DEBUG_PRINT(("NATIVE: LowLevelMouseProc - Error on the attach current thread.n"));
	return CallNextHookEx(NULL,nCode,wParam,lParam);
}

JNIEXPORT void JNICALL Java_de_ksquared_system_mouse_MouseHook_registerHook(JNIEnv * env,jobject obj,jobject _globalMouseListenerObject) {
	DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_mouse_MouseHook_registerHook - Hooking started!n"));

	HHOOK hookHandle = SetWindowsHookEx(WH_MOUSE_LL,LowLevelMouseProc,hInst,0);
	globalMouseListenerObject = _globalMouseListenerObject;
	
	if(hookHandle==NULL) {
		DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_mouse_MouseHook_registerHook - Hook failed!n"));
		return;
	} else DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_mouse_MouseHook_registerHook - Hook successfuln"));
	
	mouseHookObject = env->NewGlobalRef(obj);
	jclass cls = env->GetObjectClass(mouseHookObject);
	processMouseButtonMethod = env->GetMethodID(cls,"processButton","(ILde/ksquared/system/mouse/GlobalMouseListener;)V");
	processMouseMoveMethod = env->GetMethodID(cls,"processMove","(IILde/ksquared/system/mouse/GlobalMouseListener;)V");
	
	env->GetJavaVM(&jvm);
	hookThreadId = GetCurrentThreadId();

	MSG message;
	while(GetMessage(&message,NULL,0,0)) {
		TranslateMessage(&message);
		DispatchMessage(&message);
	}
	
	DEBUG_PRINT(((!UnhookWindowsHookEx(hookHandle))?("NATIVE: Java_de_ksquared_system_mouse_MouseHook_registerHook - Unhook failedn")
	                                                :"NATIVE: Java_de_ksquared_system_mouse_MouseHook_registerHook - Unhook successfuln"));
}

JNIEXPORT void JNICALL Java_de_ksquared_system_mouse_MouseHook_unregisterHook(JNIEnv *env,jobject object) {
	if(hookThreadId==0) return;	
	DEBUG_PRINT(("NATIVE: Java_de_ksquared_system_mouse_MouseHook_unregisterHook - call PostThreadMessage.n"));
	PostThreadMessage(hookThreadId,WM_QUIT,0,0L);
}
package de.ksquared.system.mouse;

import java.util.List;
import java.util.Vector;

public class GlobalMouseListener {
	protected PoolHook hook;
	public GlobalMouseListener() { (hook=new PoolHook(this)).start();	}
	protected List<MouseListener> listeners = new Vector<MouseListener>();

	public void addMouseListener(MouseListener listener) { listeners.add(listener); }
	public void removeMouseListener(MouseListener listener) { listeners.remove(listener); }

	void mouseMoved(MouseEvent event) {
		try {
			for(MouseListener listener:listeners)
				listener.mouseMoved(event);
		} catch(Exception e) { e.printStackTrace(); }
	}
	void mousePressed(MouseEvent event) {
		try {
			for(MouseListener listener:listeners)
				listener.mousePressed(event);
		} catch(Exception e) { e.printStackTrace(); }
	}
	void mouseReleased(MouseEvent event) {
		try {
			for(MouseListener listener:listeners)
				listener.mouseReleased(event);
		} catch(Exception e) { e.printStackTrace(); }
	}
}
package de.ksquared.system.mouse;

public class MouseAdapter implements MouseListener {
	@Override public void mouseMoved(MouseEvent event) {}
	@Override public void mousePressed(MouseEvent event) {}
	@Override public void mouseReleased(MouseEvent event) {}
}
package de.ksquared.system.mouse;

import java.util.EventObject;

public class MouseEvent extends EventObject {
	private static final long serialVersionUID = -8194688548489965445L;
	
	public static final int TRANSITION_STATE_MOVE = 1,TRANSITION_STATE_DOWN = 2,TRANSITION_STATE_UP = 3;
	public static final int BUTTON_NO = 0x0,BUTTON_LEFT = 1<<1,BUTTON_RIGHT = 1<<2;
	
	protected GlobalMouseListener listener;
	protected int transitionState,button,buttons;
	protected int x,y;

	public MouseEvent(Object source,GlobalMouseListener listener,int transitionState,int button,int buttons,int x,int y) {
		super(source);
		this.listener = listener;
		this.transitionState = transitionState;
		this.button = button;
		this.buttons = buttons;
		this.x = x;
		this.y = y;
	}
	
	public int getTransitionState() { return transitionState; }
	public int getButton() { return button; }
	public int getButtons() { return buttons; }
	public int getX() { return x; }
	public int getY() { return y; }
	
	public boolean equals(MouseEvent event) {
		return event.getButton()==button
		     &&event.getButtons()==buttons
		     &&event.getX()==x
		     &&event.getY()==y;
	}
	
	@Override public String toString() {
		StringBuilder string = new StringBuilder().append(x).append(',').append(y);
		if(buttons!=BUTTON_NO) {
			string.append(" [");
			if((buttons&BUTTON_LEFT)!=BUTTON_NO)
				string.append("left,");
			if((buttons&BUTTON_RIGHT)!=BUTTON_NO)
				string.append("right,");
			return string.deleteCharAt(string.length()-1).append(']').toString();
		} else return string.toString();
	}
}
package de.ksquared.system.mouse;

import java.awt.Dimension;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;

class PoolHook extends Thread {
	private MouseHook hook;
	private GlobalMouseListener listener;

	PoolHook(GlobalMouseListener listener) {
		this.setDaemon(true);
		this.listener = listener;
	}

	public void run() {
		hook = new MouseHook();
		hook.registerHook(listener);
	}
}

class EventProcedure extends Thread {
	private MouseHook hook;
	
	EventProcedure(MouseHook hook) {
		this.setDaemon(true);
		this.hook = hook;
	}
	
	@Override public void run() {
		while(true) {
			if(!hook.buffer.isEmpty()) {
				MouseEvent event = hook.buffer.remove(0);
				GlobalMouseListener listener = event.listener;
				switch(event.transitionState) {
				case MouseEvent.TRANSITION_STATE_DOWN:
					listener.mousePressed(event);
					break;
				case MouseEvent.TRANSITION_STATE_UP:
					listener.mouseReleased(event);
					break;
				case MouseEvent.TRANSITION_STATE_MOVE:
					listener.mouseMoved(event);
					break;
				}
			} else try { Thread.sleep(10); }	catch(InterruptedException e) { e.printStackTrace(); }
		}
	}
}

class MouseHook {	
	private static final int WM_LBUTTONDOWN = 513,WM_LBUTTONUP = 514,WM_RBUTTONDOWN = 516,WM_RBUTTONUP = 517;
	
	private int buttons,x,y;
	private static Dimension size;
	static {
		Rectangle bounds = new Rectangle();
		for(GraphicsDevice device:GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices())
			bounds.add(device.getDefaultConfiguration().getBounds());
		size = new Dimension(bounds.width,bounds.height);
	}
	
	List<MouseEvent> buffer = Collections.synchronizedList(new LinkedList<MouseEvent>());
	private EventProcedure procedure = new EventProcedure(this);
	
	public MouseHook() { if(Native.load()) procedure.start(); }
	void processButton(int parameter,GlobalMouseListener listener) {
		switch(parameter) {
		case WM_LBUTTONDOWN:
			buttons |= MouseEvent.BUTTON_LEFT;
			buffer.add(new MouseEvent(this,listener,MouseEvent.TRANSITION_STATE_DOWN,MouseEvent.BUTTON_LEFT,buttons,x,y));
			break;
		case WM_LBUTTONUP:
			buttons &= (~MouseEvent.BUTTON_LEFT);
			buffer.add(new MouseEvent(this,listener,MouseEvent.TRANSITION_STATE_UP,MouseEvent.BUTTON_LEFT,buttons,x,y));
			break;
		case WM_RBUTTONDOWN:
			buttons |= MouseEvent.BUTTON_RIGHT;
			buffer.add(new MouseEvent(this,listener,MouseEvent.TRANSITION_STATE_DOWN,MouseEvent.BUTTON_RIGHT,buttons,x,y));
			break;
		case WM_RBUTTONUP:
			buttons &= (~MouseEvent.BUTTON_RIGHT);
			buffer.add(new MouseEvent(this,listener,MouseEvent.TRANSITION_STATE_UP,MouseEvent.BUTTON_RIGHT,buttons,x,y));
			break;
		}
	}
	void processMove(int x,int y,GlobalMouseListener listener) {
		this.x = Math.min(Math.max(0,x),size.width);
		this.y = Math.min(Math.max(0,y),size.height);
		buffer.add(new MouseEvent(this,listener,MouseEvent.TRANSITION_STATE_MOVE,MouseEvent.BUTTON_NO,buttons,this.x,this.y));
	}
	
	native void registerHook(GlobalMouseListener listener);
	native void unregisterHook();
}
package de.ksquared.system.mouse;

import java.util.EventListener;

public interface MouseListener extends EventListener {
	public void mouseMoved(MouseEvent event);
  public void mousePressed(MouseEvent event);
  public void mouseReleased(MouseEvent event);
}
package de.ksquared.system.mouse;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class Native {
	private static Boolean loaded = null;

	static boolean load() {
		if(loaded!=null)
			return loaded==Boolean.TRUE;

		String libpath = System.getProperty("de.ksquared.system.mouse.lib.path"),
				   libname = System.getProperty("de.ksquared.system.mouse.lib.name");
		if(libname==null)
			libname = System.mapLibraryName("mousehook");
		try {
			if(libpath==null)
				   System.loadLibrary("mousehook");
			else System.load(new File(libpath,libname).getAbsolutePath());
			return (loaded=Boolean.TRUE);
		}	catch(UnsatisfiedLinkError e) { /* do nothing, try next */ }

		String osname = System.getProperty("os.name").toLowerCase(),
				   osarch = System.getProperty("os.arch");
		     if(osname.startsWith("mac os")) { osname = "mac"; osarch = "universal"; }
		else if(osname.startsWith("windows")) osname = "win";
		else if(osname.startsWith("sunos")) osname = "solaris";
		if(osarch.startsWith("i")&&osarch.endsWith("86"))
			osarch = "x86";
		libname = "mousehook-"+osname+'-'+osarch+".lib";
		try {
			InputStream input = Native.class.getClassLoader().getResourceAsStream(libname);
			if(input==null)
				throw new Exception("libname: "+libname+" not found");
			File temp = File.createTempFile("mousehook-",".lib");
			temp.deleteOnExit();
			OutputStream out = new FileOutputStream(temp);
			byte[] buffer = new byte[1024];
			int read;
			while((read = input.read(buffer))!=-1)
				out.write(buffer,0,read);
			input.close(); out.close();
			System.load(temp.getAbsolutePath());
			return (loaded=Boolean.TRUE);
		} catch(Exception e) { /* do nothing, go on */ }
		return (loaded=Boolean.FALSE);
	}
}

Two examples how to use the classes:

package de.ksquared.test.system.keyboard;

import de.ksquared.system.keyboard.GlobalKeyListener;
import de.ksquared.system.keyboard.KeyAdapter;
import de.ksquared.system.keyboard.KeyEvent;

public class KeyboardHookTest {
	public static void main(String[] args) {
		new GlobalKeyListener().addKeyListener(new KeyAdapter() {
			@Override public void keyPressed(KeyEvent event) { System.out.println(event); }
			@Override public void keyReleased(KeyEvent event) {
				System.out.println(event);
				if(event.getVirtualKeyCode()==KeyEvent.VK_ADD
				&& event.isCtrlPressed())
					System.out.println("CTRL+ADD was just released (CTRL is still pressed)");
			}
		});
		while(true)
			try { Thread.sleep(100); }
			catch(InterruptedException e) { e.printStackTrace(); }
	}
}
package de.ksquared.test.system.mouse;

import de.ksquared.system.mouse.GlobalMouseListener;
import de.ksquared.system.mouse.MouseAdapter;
import de.ksquared.system.mouse.MouseEvent;

public class MouseHookTest {
	public static void main(String[] args) {
		new GlobalMouseListener().addMouseListener(new MouseAdapter() {
			@Override public void mousePressed(MouseEvent event)  { System.out.println(event); }
			@Override public void mouseReleased(MouseEvent event)  { System.out.println(event); }
			@Override public void mouseMoved(MouseEvent event) {
				System.out.println(event);
				if((event.getButtons()&MouseEvent.BUTTON_LEFT)!=MouseEvent.BUTTON_NO
				&& (event.getButtons()&MouseEvent.BUTTON_RIGHT)!=MouseEvent.BUTTON_NO)
					System.out.println("Both mouse buttons are currenlty pressed!");
			}
		});
		while(true)
			try { Thread.sleep(100); }
			catch(InterruptedException e) { e.printStackTrace(); }
	}
}

Have fun! Don’t forget to Since version 0.2 it is possible, but no longer necessary to download the “KeyboardHook.dll” and/or “MouseHook.dll” libraries and specify the libary path when starting your JVM using:

java ... -Djava.library.path="C:/absolute/path/to/mousehook.dll, C:/absolute/path/to/keyboardhook.dll"

(If you specify the virtual machine parameter instead of using the bundled libraries it might gets you a little performance enhance)

Update (7.16.11): A new version (0.2) is out now. At first I lowercased the library names, which should sove some issues loading the libraries. The major change regards the loading mechanism of the libraries. With the new Native classes, it is no longer necesarry to specify the virtual machine parameter, because the libraries are bundled into the KeyboardHook and MouseHook archives. I think this is a great advantage, especially if you plan to bundle your application. The MouseHook.h/.cpp files are now present in the source bundle aswell. Thanks to Adam.

Update (7.21.11): I made a minor change in version (0.2.1). Due to a missing linker flag, the virtual machine was not able to load the native libraries on some systems. An java.lang.UnsatisfiedLinkError was thrown. This issue should be solved now.

Used compiler flags: -I”%JAVA_HOME%\include” -I”%JAVA_HOME%\include\win32″ -O0 -g3 -Wall -c -fmessage-length=0 -mno-cygwin -D_JNI_IMPLEMENTATION_
Used linker flags: -Wl,–kill-at -mno-cygwin -shared -static-libgcc -shared

Thanks a lot to Alexander Loob and Timo.

Update (10.11.11): Version 0.3 released! Now compiled and tested on a 32 & 64 bit JVM. Libraries have been splitted into a 32 & 64 bit DLL. Before an java.lang.UnsatisfiedLinkError was thrown on 64 bit JVM’s. This issue should be solved now. Also the library has been compiled using Java 7.0 native libs. Packaged bundles now also include the 64 bit DLL.

Thanks to Timo, Thomas, Byron, Lucas, Jason and Frank.