I wanted to try using NativeHook, which can be written entirely in Java, instead of JNativeHook, so I wrote some test code. However, the output is always the same no matter which key I press. Can someone tell me how to get the correct key code?
import java.lang.foreign.*;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public class hook_test {
private static final int WH_KEYBOARD_LL = 13;
private static final int WM_KEYDOWN = 0x0100;
private static long hook;
private static MethodHandle callNextHookEx;
void main() throws Throwable {
System.loadLibrary("user32");
Linker linker = Linker.nativeLinker();
SymbolLookup user32Lookup = SymbolLookup.loaderLookup();
MethodHandle setWindowsHookEx = linker.downcallHandle(user32Lookup.find("SetWindowsHookExA").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT));
callNextHookEx = linker.downcallHandle(user32Lookup.find("CallNextHookEx").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG));
MethodHandle getMessage = linker.downcallHandle(user32Lookup.find("GetMessageA").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.ADDRESS, ValueLayout.ADDRESS, ValueLayout.JAVA_INT, ValueLayout.JAVA_INT));
MethodHandle unhookWindowsHookEx = linker.downcallHandle(user32Lookup.find("UnhookWindowsHookEx").orElseThrow(), FunctionDescriptor.of(ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG));
MethodHandle hookProcHandle = MethodHandles.lookup().findStatic(hook_test.class, "hookProc", MethodType.methodType(long.class, int.class, long.class, long.class));
MemorySegment hookProcAddress = linker.upcallStub(hookProcHandle, FunctionDescriptor.of(ValueLayout.JAVA_LONG, ValueLayout.JAVA_INT, ValueLayout.JAVA_LONG, ValueLayout.JAVA_LONG), Arena.ofAuto());
hook = (long) setWindowsHookEx.invoke(WH_KEYBOARD_LL, hookProcAddress, MemorySegment.NULL, 0);
if (hook == 0) {
System.out.println("Failed to set hook");
return;
}
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
if (hook != 0)
unhookWindowsHookEx.invoke(hook);
} catch (Throwable t) {
System.err.println(t.getMessage());
}
}));
try (Arena arena = Arena.ofAuto()) {
MemorySegment msg = arena.allocate(28);
while ((int) getMessage.invoke(msg, MemorySegment.NULL, 0, 0) != 0) {
}
}
}
public static long hookProc(int code, long wParam, long lParam) {
if (code >= 0) {
if (wParam == WM_KEYDOWN)
System.out.printf("int code:%s\tlong wParam:%s\tlong lParam:%s\n", code, wParam, lParam);
}
try {
return (long) callNextHookEx.invoke(hook, code, wParam, lParam);
} catch (Throwable t) {
System.err.println(t.getMessage());
return 0;
}
}
}
I expected that even if the correct key code was not obtained, a different value would be output from lParam for each key input. However, when I tried to run it, all the values were the same and they changed every time I launched it.
int code:0 long wParam:256 long lParam:1059701387608
int code:0 long wParam:256 long lParam:1059701387608
int code:0 long wParam:256 long lParam:1059701387608
int code:0 long wParam:256 long lParam:1059701387608
As greg-449 suggested in his comment, the two parameters shouldn't be longs. This is what my MinGW includes show:
typedef LRESULT (CALLBACK *HOOKPROC)(int code,WPARAM wParam,LPARAM lParam);
typedef UINT_PTR WPARAM;
typedef LONG_PTR LPARAM;
In other words - one is a pointer to an int, the other a pointer to a long. In your code you should use MemoryLayout
as types, and get their int and long values.
Note that LRESULT
also isn't a long, it's a pointer to a long:
typedef LONG_PTR LRESULT;
That means you have to change the return type of both callNextHookEx
and hookProc
. The latter can return the MemorySegment
returned by callNextHookEx
or MemorySegment.NULL
on failure.