🌐 Cross-platform Java input handling without JNI
How input4j leverages the Foreign Function & Memory API for modern, dependency-free input handling
Table of Contents
Traditional Challenges with JNI
Before the Foreign Function & Memory API, Java developers had limited options for accessing native input APIs:
| Approach | Pros | Cons |
|---|---|---|
| Java AWT Robot | ✅ Simple API | ❌ Limited to basic input |
| JNI | ✅ Full native access | ❌ Complex setup, platform-specific |
| Third-party libs | ✅ Pre-built solutions | ❌ Additional dependencies |
// Traditional JNI approach
public class NativeInput {
static {
System.loadLibrary("native-input");
}
private native void init();
private native int getButtonCount();
private native boolean isButtonPressed(int button);
public static void main(String[] args) {
NativeInput input = new NativeInput();
input.init();
// ... complex native code required
}
}
The Foreign Function & Memory API
The Foreign Function & Memory API (FFM API), introduced in Java 22, provides a modern, safe way to interact with native code:
Type Safety
Compile-time checking of native function signatures
Memory Safety
Automatic memory management and bounds checking
Performance
Direct memory access without JNI overhead
No Native Artifacts
No .dll, .so, or .dylib files required
// Modern FFM API approach
import jdk.incubator.foreign.MemorySegment;
import jdk.incubator.foreign.SymbolLookup;
public class ModernInput {
private static final SymbolLookup LOOKUP = SymbolLookup.loaderLookup();
public void init() {
MemorySegment lib = LOOKUP.lookup("XInputGetState")
.orElseThrow()
.downcallHandle(
MethodType.methodType(int.class, int.class, MemorySegment.class)
);
// ... safe, type-checked native calls
}
}
Platform Support in input4j
input4j uses the FFM API to provide unified input handling across platforms:
Windows
DirectInput & XInput via native APIs
Linux
evdev interface for input events
macOS
IOKit HID device access
Performance Comparison
input4j's FFM API approach offers significant performance advantages:
Implementation Details
input4j's architecture demonstrates best practices for FFM API usage:
public class InputDevices {
private static final Map<String, InputDevice> devices = new ConcurrentHashMap<>();
public static InputDevice getDevice(String id) {
return devices.computeIfAbsent(id, InputDevices::createDevice);
}
private static InputDevice createDevice(String id) {
// Platform-specific implementation using FFM API
return switch (Platform.getCurrent()) {
case WINDOWS -> new WindowsInputDevice(id);
case LINUX -> new LinuxInputDevice(id);
case MACOS -> new MacOSInputDevice(id);
default -> throw new UnsupportedOperationException();
};
}
}
Getting Started
Ready to use input4j in your project? Here's how to get started:
Add Dependency
dependencies {
implementation 'de.gurkenlabs:input4j:1.1.1'
}
Initialize Devices
try (var devices = InputDevices.init()) {
for (var device : devices.getAll()) {
System.out.println("Found: " + device.getName());
}
}
Handle Input
device.onButtonPressed(XInput.X, () ->
System.out.println("X button pressed"));
device.onAxisChanged(Axis.AXIS_X, value ->
System.out.println("X axis: " + value));
</div>