Javaでネイティブにキーボード/マウスイベントを取得する

Javaでキーボード入力をキャプチャしたい！ でもGUIフレームワークの機能はフォーカスがないと駄目だからバックグラウンドからじゃ使えないし･･････って思ったこと、ありませんか？ 私はあります。

そこで使えるのが[JNativeHook](https://code.google.com/p/jnativehook/)というライブラリです。プラットフォームに依存したネイティブでいい感じにアレしてくれるらしいです。日本語の解説がなかったのでざっと書いてみます。

# 動作確認環境
Windows7 64bit
IntelliJ IDEA 13.1.4
JDK 1.8

# 準備
[Githubで管理されている](https://github.com/kwhat/jnativehook)ようなので、[releases](https://github.com/kwhat/jnativehook/releases)ページから適当にダウンロードしてクラスパスを通します。クラスパスの通し方とかは他のサイトを見てください。ダウンロードしたzipはそのままではなく解凍してjarとかを取り出す必要があるので注意。

# 共通
`org.jnativehook.GlobalScreen.registerNativeHook()`を呼び出してネイティブへのフックを有効にして、`org.jnativehook.GlobalScreen.getInstance()`で得た`GlobalScreen`インスタンスに適切なインターフェースを実装したクラスのインスタンスを登録する、という流れです。

イベントの文字列表現は`toString()`ではなく`paramString()`です。何故。

あと、そのままだとライブラリが吐き出すログがスゴイので動作確認ができたら抑制しておきましょう。`java.util.Logger`を使っているので、JNativeHookが使っているLoggerのインスタンスを取得してログレベルを`WARNING`にします。ただ、時間差でログレベルが再度セットされたりするっぽいので、少し待ってからのほうがいいかも。以下に例を示します。

```java
/**
 * 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`インターフェースを実装して使用します。

```java
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つに分かれてたりして地味に面倒。

```java
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() は省略
}
```