Skip to content

Commit 27296fa

Browse files
committed
Add implementation of windows hook using JNA.
1 parent a921180 commit 27296fa

3 files changed

Lines changed: 208 additions & 8 deletions

File tree

src/org/simplenativehooks/NativeHookInitializer.java

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -7,25 +7,66 @@
77
import org.simplenativehooks.osx.GlobalOSXEventOchestrator;
88
import org.simplenativehooks.utilities.OSIdentifier;
99
import org.simplenativehooks.windows.GlobalWindowsEventOchestrator;
10+
import org.simplenativehooks.windows.GlobalWindowsJnaEventOchestrator;
1011
import org.simplenativehooks.x11.GlobalX11EventOchestrator;
1112

1213
public class NativeHookInitializer {
1314

1415
private static final Logger LOGGER = Logger.getLogger(NativeHookInitializer.class.getName());
15-
public static final String VERSION = "0.0.2";
16+
public static final String VERSION = "0.0.4";
1617
public static final boolean USE_X11_ON_LINUX = true;
1718

18-
private static final NativeHookInitializer INSTANCE = new NativeHookInitializer();
19+
private final Config config;
1920

20-
private NativeHookInitializer() {}
21+
private NativeHookInitializer(Config config) {
22+
this.config = config;
23+
}
2124

2225
public static NativeHookInitializer of() {
23-
return INSTANCE;
26+
return new NativeHookInitializer(Config.Builder.of().useJnaForWindows(true).build());
27+
}
28+
29+
public static NativeHookInitializer of(Config config) {
30+
return new NativeHookInitializer(config);
31+
}
32+
33+
public static class Config {
34+
private boolean useJnaForWindows;
35+
36+
private Config(boolean useJnaForWindows) {
37+
this.useJnaForWindows = useJnaForWindows;
38+
}
39+
40+
public static class Builder {
41+
private boolean useJnaForWindows;
42+
43+
private Builder() {}
44+
45+
public static Builder of() {
46+
return new Builder();
47+
}
48+
49+
/**
50+
* If JNA is used for Windows, there is no need to call resource extraction.
51+
*/
52+
public Builder useJnaForWindows(boolean useJnaForWindows) {
53+
this.useJnaForWindows = useJnaForWindows;
54+
return this;
55+
}
56+
57+
public Config build() {
58+
return new Config(useJnaForWindows);
59+
}
60+
}
2461
}
2562

2663
public void start() {
2764
if (OSIdentifier.IS_WINDOWS) {
28-
GlobalWindowsEventOchestrator.of().start();
65+
if (config.useJnaForWindows) {
66+
GlobalWindowsJnaEventOchestrator.of().start();
67+
} else {
68+
GlobalWindowsEventOchestrator.of().start();
69+
}
2970
return;
3071
}
3172
if (OSIdentifier.IS_LINUX) {
@@ -48,7 +89,11 @@ public void start() {
4889
public void stop() {
4990
if (OSIdentifier.IS_WINDOWS) {
5091
try {
51-
GlobalWindowsEventOchestrator.of().stop();
92+
if (config.useJnaForWindows) {
93+
GlobalWindowsJnaEventOchestrator.of().stop();
94+
} else {
95+
GlobalWindowsEventOchestrator.of().stop();
96+
}
5297
} catch (InterruptedException e) {
5398
LOGGER.log(Level.WARNING, "Interrupted while stopping.", e);
5499
}
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
package org.simplenativehooks.windows;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
import java.util.logging.Logger;
6+
7+
import org.simplenativehooks.NativeHookGlobalEventPublisher;
8+
9+
import com.sun.jna.Pointer;
10+
import com.sun.jna.Structure;
11+
import com.sun.jna.platform.win32.BaseTSD.ULONG_PTR;
12+
import com.sun.jna.platform.win32.Kernel32;
13+
import com.sun.jna.platform.win32.User32;
14+
import com.sun.jna.platform.win32.WinDef.HMODULE;
15+
import com.sun.jna.platform.win32.WinDef.HWND;
16+
import com.sun.jna.platform.win32.WinDef.LPARAM;
17+
import com.sun.jna.platform.win32.WinDef.LRESULT;
18+
import com.sun.jna.platform.win32.WinDef.POINT;
19+
import com.sun.jna.platform.win32.WinDef.WPARAM;
20+
import com.sun.jna.platform.win32.WinUser;
21+
import com.sun.jna.platform.win32.WinUser.HHOOK;
22+
import com.sun.jna.platform.win32.WinUser.HOOKPROC;
23+
import com.sun.jna.platform.win32.WinUser.KBDLLHOOKSTRUCT;
24+
import com.sun.jna.platform.win32.WinUser.LowLevelKeyboardProc;
25+
import com.sun.jna.platform.win32.WinUser.MSG;
26+
27+
public class GlobalWindowsJnaEventOchestrator {
28+
29+
private static final Logger LOGGER = Logger.getLogger(GlobalWindowsJnaEventOchestrator.class.getName());
30+
31+
private static GlobalWindowsJnaEventOchestrator INSTANCE = new GlobalWindowsJnaEventOchestrator();
32+
33+
public static GlobalWindowsJnaEventOchestrator of() {
34+
return INSTANCE;
35+
}
36+
37+
private GlobalWindowsJnaEventOchestrator() {}
38+
39+
public interface LowLevelMouseProc extends HOOKPROC {
40+
LRESULT callback(int nCode, WPARAM wParam, MOUSEHOOKSTRUCT lParam);
41+
}
42+
43+
public static class MOUSEHOOKSTRUCT extends Structure {
44+
public class ByReference extends MOUSEHOOKSTRUCT implements Structure.ByReference {
45+
};
46+
47+
public POINT pt;
48+
public HWND hwnd;
49+
public int wHitTestCode;
50+
public ULONG_PTR dwExtraInfo;
51+
52+
@Override
53+
protected List<String> getFieldOrder() {
54+
return Arrays.asList("pt", "hwnd", "wHitTestCode", "dwExtraInfo");
55+
}
56+
}
57+
58+
private HHOOK mouseHHK, keyboardHHK; // Hook handlers.
59+
private LowLevelMouseProc mouseHook;
60+
private LowLevelKeyboardProc keyboardHook;
61+
62+
private boolean done;
63+
public Thread driverThread;
64+
65+
private void setHook() {
66+
HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
67+
mouseHHK = User32.INSTANCE.SetWindowsHookEx(WinUser.WH_MOUSE_LL, mouseHook, hMod, 0);
68+
keyboardHHK = User32.INSTANCE.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, keyboardHook, hMod, 0);
69+
}
70+
71+
private void unhook() {
72+
User32.INSTANCE.UnhookWindowsHookEx(keyboardHHK);
73+
User32.INSTANCE.UnhookWindowsHookEx(mouseHHK);
74+
}
75+
76+
public void start() {
77+
keyboardHook = new LowLevelKeyboardProc() {
78+
@Override
79+
// See reference at:
80+
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644985(v=vs.85).aspx
81+
public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT lParam) {
82+
if (nCode >= 0) {
83+
int w = wParam.intValue();
84+
// w can have the following values.
85+
// WinUser.WM_KEYDOWN, WinUser.WM_SYSKEYDOWN (for Alt key).
86+
// WinUser.WM_KEYUP, WinUser.WM_SYSKEYUP
87+
88+
NativeHookGlobalEventPublisher.of().publishKeyEvent(WindowsNativeKeyEvent.of(lParam.vkCode, w));
89+
}
90+
91+
Pointer ptr = lParam.getPointer();
92+
long peer = Pointer.nativeValue(ptr);
93+
return User32.INSTANCE.CallNextHookEx(keyboardHHK, nCode, wParam, new LPARAM(peer));
94+
}
95+
};
96+
97+
mouseHook = new LowLevelMouseProc() {
98+
@Override
99+
// See reference at:
100+
// http://msdn.microsoft.com/en-us/library/windows/desktop/ms644986(v=vs.85).aspx
101+
public LRESULT callback(int nCode, WPARAM wParam, MOUSEHOOKSTRUCT lParam) {
102+
if (nCode >= 0) {
103+
NativeHookGlobalEventPublisher.of().publishMouseEvent(WindowsNativeMouseEvent.of(lParam.pt.x, lParam.pt.y, wParam.intValue()));
104+
}
105+
106+
Pointer ptr = lParam.getPointer();
107+
long peer = Pointer.nativeValue(ptr);
108+
return User32.INSTANCE.CallNextHookEx(mouseHHK, nCode, wParam, new LPARAM(peer));
109+
}
110+
};
111+
112+
done = false;
113+
driverThread = new Thread(){
114+
@Override
115+
public void run() {
116+
setHook();
117+
118+
MSG msg = new MSG();
119+
// Message loop. This needs to be run in the same thread as the one
120+
// calling setHook().
121+
// In fact, the while loop is not executed once, the code will block
122+
// at in the GetMessage function most of the time.
123+
while (!done) {
124+
int result = User32.INSTANCE.GetMessage(msg, null, 0, 0);
125+
if (result == 0) {
126+
break;
127+
}
128+
129+
if (result == -1) {
130+
LOGGER.severe("GetMessage has error.");
131+
done = true;
132+
unhook();
133+
break;
134+
} else {
135+
User32.INSTANCE.TranslateMessage(msg);
136+
User32.INSTANCE.DispatchMessage(msg);
137+
}
138+
}
139+
}
140+
};
141+
LOGGER.info("Starting JNA even hook thread...");
142+
driverThread.setDaemon(true);
143+
driverThread.start();
144+
LOGGER.info("JNA even hook thread started.");
145+
}
146+
147+
public void stop() throws InterruptedException {
148+
LOGGER.info("Stopping JNA even hook.");
149+
done = true;
150+
unhook();
151+
LOGGER.info("Stopped JNA even hook.");
152+
}
153+
}

src/org/simplenativehooks/windows/WindowsNativeKeyEvent.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ public static WindowsNativeKeyEvent of(int code, int param) {
2424
public NativeKeyEvent convertEvent() throws InvalidKeyEventException {
2525
boolean pressed = false;
2626
switch (param) {
27-
case 256:
27+
case 256: // WM_KEYDOWN
28+
case 260: // WM_SYSKEYDOWN, triggered for Alt key.
2829
pressed = true;
2930
break;
30-
case 257:
31+
case 257: // WM_KEYUP
32+
case 261: // WM_SYSKEYUP
3133
pressed = false;
3234
break;
3335
default:

0 commit comments

Comments
 (0)