Javaでキーボード入力をキャプチャしたい! でもGUIフレームワークの機能はフォーカスがないと駄目だからバックグラウンドからじゃ使えないし・・・・・・って思ったこと、ありませんか? 私はあります。
そこで使えるのがJNativeHookというライブラリです。プラットフォームに依存したネイティブでいい感じにアレしてくれるらしいです。日本語の解説がなかったのでざっと書いてみます。
動作確認環境
Windows7 64bit
IntelliJ IDEA 13.1.4
JDK 1.8
準備
Githubで管理されているようなので、releasesページから適当にダウンロードしてクラスパスを通します。クラスパスの通し方とかは他のサイトを見てください。ダウンロードしたzipはそのままではなく解凍してjarとかを取り出す必要があるので注意。
共通
org.jnativehook.GlobalScreen.registerNativeHook()
を呼び出してネイティブへのフックを有効にして、org.jnativehook.GlobalScreen.getInstance()
で得たGlobalScreen
インスタンスに適切なインターフェースを実装したクラスのインスタンスを登録する、という流れです。
イベントの文字列表現はtoString()
ではなくparamString()
です。何故。
あと、そのままだとライブラリが吐き出すログがスゴイので動作確認ができたら抑制しておきましょう。java.util.Logger
を使っているので、JNativeHookが使っているLoggerのインスタンスを取得してログレベルをWARNING
にします。ただ、時間差でログレベルが再度セットされたりするっぽいので、少し待ってからのほうがいいかも。以下に例を示します。
/**
* JNativeHookのロギングを抑制する。
*
* mainで一度設定してもそのあとでJNativeHook側でセットされるっぽいので別スレッドで2秒待ってセット。
*/
private static void suppressLogger() {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1, r -> {
Thread thread = new Thread(r);
thread.setDaemon(true);
return thread;
});
executorService.schedule(() -> {
final Logger jNativeHookLogger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
if (jNativeHookLogger.getLevel() != Level.WARNING) {
synchronized (jNativeHookLogger) {
jNativeHookLogger.setLevel(Level.WARNING);
}
}
}, 2, TimeUnit.SECONDS);
}
キーボードイベント
キーボードイベントはorg.jnativehook.keyboard.NativeKeyListener
インターフェースを実装して使用します。
public class Main implements NativeKeyListener {
// キーが押された
@Override
public void nativeKeyPressed(NativeKeyEvent e) {
System.out.println(e.paramString());
}
// キーが離された
@Override
public void nativeKeyReleased(NativeKeyEvent e) {
System.out.println(e.paramString());
}
// キーをタイプした
@Override
public void nativeKeyTyped(NativeKeyEvent e) {
System.out.println(e.paramString());
}
public static void main(String[] args) {
// フックしてなかったらフック
if (!GlobalScreen.isNativeHookRegistered()) {
try {
GlobalScreen.registerNativeHook();
} catch (NativeHookException e) {
e.printStackTrace();
System.exit(-1);
}
}
// ログを抑制してハンドラを登録
suppressLogger();
GlobalScreen.getInstance().addNativeKeyListener(new Main());
}
// suppressLogger() は省略
}
マウスイベント
各マウスイベントとインターフェースの対応表
イベント | クラス名 |
---|---|
クリック | org.jnativehook.mouse.NativeMouseListener |
移動・ドラッグ | org.jnativehook.mouse.NativeMouseMotionListener |
上記2つのmixin | org.jnativehook.mouse.NativeMouseInputListener |
ホイール | org.jnativehook.mouse.NativeMouseWheelListener |
上記から使いたいインターフェースを実装して使用します。ホイールイベントが仲間外れだったりインスタンスの登録が3つに分かれてたりして地味に面倒。
public class Main implements NativeMouseInputListener, NativeMouseWheelListener {
// マウスをクリックした
@Override
public void nativeMouseClicked(NativeMouseEvent e) {
System.out.println(e.paramString());
}
// マウスを押した
@Override
public void nativeMousePressed(NativeMouseEvent e) {
System.out.println(e.paramString());
}
// マウスを離した
@Override
public void nativeMouseReleased(NativeMouseEvent e) {
System.out.println(e.paramString());
}
// マウスを移動した
@Override
public void nativeMouseMoved(NativeMouseEvent e) {
System.out.println(e.paramString());
}
// マウスをドラッグした
@Override
public void nativeMouseDragged(NativeMouseEvent e) {
System.out.println(e.paramString());
}
// マウスホイールを動かした
@Override
public void nativeMouseWheelMoved(NativeMouseWheelEvent e) {
System.out.println(e.paramString());
}
public static void main(String[] args) {
// フックしてなかったらフック
if (!GlobalScreen.isNativeHookRegistered()) {
try {
GlobalScreen.registerNativeHook();
} catch (NativeHookException e) {
e.printStackTrace();
System.exit(-1);
}
}
// ログを抑制してハンドラを登録
suppressLogger();
final Main handler = new Main();
final GlobalScreen globalScreen = GlobalScreen.getInstance();
globalScreen.addNativeMouseListener(handler);
globalScreen.addNativeMouseMotionListener(handler);
globalScreen.addNativeMouseWheelListener(handler);
}
// suppressLogger() は省略
}