标准的Java键盘事件监听器(KeyListener)和鼠标事件监听器(MouseListener)只能在该Java程序聚焦的时候监听事件。要想让你的Java程序能够在系统后台跟踪全局键盘和鼠标事件,那就需要使用JNI(Java Native Interface)来创建一个钩子监听操作系统的事件了。本文只讨论,Java程序与Windows操作系统的交互,如果你知道如何实现Java监听Linux事件,请留言,谢谢。开发运行环境:Windows XP SP3, Java 1.6_15, Eclipse 3.5

直接上代码

SysHook.cpp:

[cpp]

#include <windows.h>
#include "SysHook.h"
#include <jni.h>

HINSTANCE hInst = NULL;

JavaVM * jvm = NULL;
jobject hookObj_kb = NULL;
jobject hookObj_ms = NULL;
jobject g_kl = NULL;

jmethodID processKeyID_kb = NULL;
jmethodID processKeyID_ms = NULL;
DWORD hookThreadId = 0;

LONG    g_mouseLocX = -1;    // x-location of mouse position
LONG    g_mouseLocY = -1;    // y-location of mouse position

extern "C"
BOOL APIENTRY DllMain(HINSTANCE _hInst, DWORD reason, LPVOID reserved)
{
switch (reason)
{
case DLL_PROCESS_ATTACH:
printf("C++: DllMain – DLL_PROCESS_ATTACH.n");
hInst = _hInst;
break;
default:
break;
}

return TRUE;
}

LRESULT CALLBACK MouseTracker(int nCode, WPARAM wParam, LPARAM lParam)
{
JNIEnv * env;
KBDLLHOOKSTRUCT * p = (KBDLLHOOKSTRUCT *)lParam;

if (jvm->AttachCurrentThread((void **)&env, NULL) >= 0)
{

if (nCode==HC_ACTION)
{
MOUSEHOOKSTRUCT* pStruct = (MOUSEHOOKSTRUCT*)lParam;
if (pStruct->pt.x != g_mouseLocX || pStruct->pt.y != g_mouseLocY)
{
env->CallVoidMethod(hookObj_ms, processKeyID_ms, (jint)pStruct->pt.x,(jint)pStruct->pt.y, g_kl);
g_mouseLocX = pStruct->pt.x;
g_mouseLocY = pStruct->pt.y;
}

}

}
else
{
printf("C++: LowLevelKeyboardProc – Error on the attach current thread.n");
}

return CallNextHookEx(NULL, nCode, wParam, lParam);
}

LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
JNIEnv * env;
KBDLLHOOKSTRUCT * p = (KBDLLHOOKSTRUCT *)lParam;

if (jvm->AttachCurrentThread((void **)&env, NULL) >= 0)
{
switch (wParam)
{
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
env->CallVoidMethod(hookObj_kb, processKeyID_kb, (jboolean)TRUE, p->vkCode,g_kl);
break;
case WM_KEYUP:
case WM_SYSKEYUP:
env->CallVoidMethod(hookObj_kb, processKeyID_kb, (jboolean)FALSE, p->vkCode,g_kl);
break;
default:
break;
}
}
else
{
printf("C++: LowLevelKeyboardProc – Error on the attach current thread.n");
}

return CallNextHookEx(NULL, nCode, wParam, lParam);
}

void MsgLoop()
{
MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
}

JNIEXPORT void JNICALL Java_SysHook_registerHook(JNIEnv * env, jobject obj,jobject kl)
{
HHOOK hookHandle_ms = SetWindowsHookEx(WH_MOUSE_LL, MouseTracker, hInst, 0);
HHOOK hookHandle_kb = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, hInst, 0);

g_kl = kl;

if (hookHandle_ms == NULL)
{
printf("C++: Java_SysHook_registerKeyHook – Hook failed!n");
return;
}
else
{
printf("C++: Java_SysHook_registerKeyHook – Hook successfuln");
}

if (hookHandle_kb == NULL)
{
printf("C++: Java_SysHook_registerKeyHook – Hook failed!n");
return;
}
else
{
printf("C++: Java_SysHook_registerKeyHook – Hook successfuln");
}

hookObj_kb = env->NewGlobalRef(obj);
jclass cls_kb = env->GetObjectClass(hookObj_kb);
processKeyID_kb = env->GetMethodID(cls_kb, "processKey", "(ZILGlobalEventListener;)V");

hookObj_ms = env->NewGlobalRef(obj);
jclass cls_ms = env->GetObjectClass(hookObj_ms);
processKeyID_ms = env->GetMethodID(cls_ms, "mouseMoved", "(IILGlobalEventListener;)V");

env->GetJavaVM(&jvm);
hookThreadId = GetCurrentThreadId();

MsgLoop();

if (!UnhookWindowsHookEx(hookHandle_kb))
{
printf("C++: Java_SysHook_registerKeyHook – Unhook failedn");
}
else
{
printf("C++: Java_SysHook_registerKeyHook – Unhook successfuln");
}

if (!UnhookWindowsHookEx(hookHandle_ms))
{
printf("C++: Java_SysHook_registerKeyHook – Unhook failedn");
}
else
{
printf("C++: Java_SysHook_registerKeyHook – Unhook successfuln");
}
}

JNIEXPORT void JNICALL Java_SysHook_unRegisterHook(JNIEnv *env, jobject object)
{
if (hookThreadId == 0)
return;

printf("C++: Java_SysHook_unRegisterKeyHook – call PostThreadMessage.n");
PostThreadMessage(hookThreadId, WM_QUIT, 0, 0L);
}

[/cpp]

SysHook.h:

[cpp]

/* DO NOT EDIT THIS FILE – it is machine generated */
#include <jni.h>
/* Header for class SysHook */

#ifndef _Included_SysHook
#define _Included_SysHook
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class:     SysHook
* Method:    registerHook
* Signature: (LGlobalEventListener;)V
*/
JNIEXPORT void JNICALL Java_SysHook_registerHook  (JNIEnv *, jobject, jobject);

/*
* Class:     SysHook
* Method:    unRegisterHook
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_SysHook_unRegisterHook  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif

[/cpp]

KeyboardEventListener.java

[java]

import java.util.*;

public interface KeyboardEventListener extends EventListener {
public void GlobalKeyPressed(KeyboardEvent event);

public void GlobalKeyReleased(KeyboardEvent event);
}

class KeyboardEvent extends EventObject {
private static final long serialVersionUID = 2341653211621224652L;
boolean ts, ap, ek;
int vk;

public KeyboardEvent(Object source, boolean ts, int vk, boolean ap,
boolean ek) {
super(source);
this.ts = ts;
this.vk = vk;
this.ap = ap;
this.ek = ek;
}

public boolean getTransitionState() {
return ts;
}

public long getVirtualKeyCode() {
return vk;
}

public boolean isAltPressed() {
return ap;
}

public boolean isExtendedKey() {
return ek;
}

public boolean equals(KeyboardEvent event) {
if (event.getVirtualKeyCode() == vk) {
if (event.isExtendedKey() == ek) {
if (event.isAltPressed() == ap) {
return true;
}
}
}
return false;
}
}

[/java]

MouseEventListenter.java

[java]

import java.util.*;

public interface MouseEventListener extends EventListener {
public void GlobalMouseX(MouseEvent event);

public void GlobalMouseY(MouseEvent event);
}

class MouseEvent extends EventObject {

private static final long serialVersionUID = 14654L;
int cord_x, cord_y;

public MouseEvent(Object source, int cord_x, int cord_y) {
super(source);
this.cord_x = cord_x;
this.cord_y = cord_y;
}

public int getMouseX() {
return cord_x;
}

public int getMouseY() {
return cord_y;
}

}

[/java]

GlobalEventListener.java :

[java]

public class GlobalEventListener {
PoolHook pt;

public GlobalEventListener() {
pt = new PoolHook(this);
pt.start();

}

protected javax.swing.event.EventListenerList listenerList = new javax.swing.event.EventListenerList();

public void addKeyboardEventListener(KeyboardEventListener listener) {
listenerList.add(KeyboardEventListener.class, listener);
}

public void removeKeyboardEventListener(KeyboardEventListener listener) {
listenerList.remove(KeyboardEventListener.class, listener);
}

public void addMouseEventListener(MouseEventListener listener) {
listenerList.add(MouseEventListener.class, listener);
}

public void removeMouseEventListener(MouseEventListener listener) {
listenerList.remove(MouseEventListener.class, listener);
}

void keyPressed(KeyboardEvent event) {
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2) {
if (listeners[i] == KeyboardEventListener.class) {
((KeyboardEventListener) listeners[i + 1])
.GlobalKeyPressed(event);
}
}
}

void mouseMoved(MouseEvent event) {
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2) {
if (listeners[i] == MouseEventListener.class) {
((MouseEventListener) listeners[i + 1]).GlobalMouseX(event);
((MouseEventListener) listeners[i + 1]).GlobalMouseY(event);
}
}
}

void keyReleased(KeyboardEvent event) {
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i += 2) {
if (listeners[i] == KeyboardEventListener.class) {
((KeyboardEventListener) listeners[i + 1])
.GlobalKeyReleased(event);
}
}
}

}

[/java]

SysHook.java:

[java]

class PoolHook extends Thread {
SysHook hook;
GlobalEventListener g_gl;

PoolHook(GlobalEventListener gl) {
g_gl = gl;
}

public void run() {
hook = new SysHook();
hook.registerHook(g_gl);
}

}

class SysHook {

static {
System.loadLibrary("SysHook");
}

void processKey(boolean ts, int vk, GlobalEventListener gl) {
KeyboardEvent event = new KeyboardEvent(this, ts, vk, false, false);
gl.keyPressed(event);
}

void mouseMoved(int cord_x, int cord_y, GlobalEventListener gl) {
MouseEvent event = new MouseEvent(this, cord_x, cord_y);
gl.mouseMoved(event);
}

native void registerHook(GlobalEventListener gl);

native void unRegisterHook();

}

[/java]

Example.java:

[java]

public class Example implements KeyboardEventListener, MouseEventListener {

static GlobalEventListener gl;

public static void main(String[] args) throws Exception {
Example inst = new Example();
gl = new GlobalEventListener();
gl.addKeyboardEventListener(inst);
gl.addMouseEventListener(inst);
}

@Override
public void GlobalKeyPressed(KeyboardEvent event) {

System.out.println("Key Pressed: " + event.getVirtualKeyCode());
}

@Override
public void GlobalKeyReleased(KeyboardEvent event) {
}

@Override
public void GlobalMouseX(MouseEvent event) {
System.out.println("Mouse X: " + event.getMouseX());

}

@Override
public void GlobalMouseY(MouseEvent event) {
System.out.println("Mouse Y: " + event.getMouseY());
}

}

[/java]

C++文件需要用Visual Studio编译为你的目标系统的DLL文件。如果是标准32位Windows XP,可以在这里下载已编译的文件。

在Eclipse创建工程后,需要做的设置是将该DLL所在目录添加到Native library location如图:

本文摘录、翻译并修改自http://www.jotschi.de/?p=90