JavaFX and jnativehook library conflict" → "JavaFX & jnativehook clash

Problem

I’m using jnativehook for global keyboard listening with a JavaFX 11 GUI. I have a TextField to define the name of a file created after pressing a specified key combination. The problem is, I cannot edit the text in the TextField by keyboard. I can delete or paste text by mouse but not by keyboard.

I created an individual thread for global keyboard listening, but my attempts to stop the thread when the TextField is focused have failed.

Minimal Reproducible Example

public class Main extends Application {
    private static final int APP_WIDTH = 400;
    private static final int APP_HEIGHT = 400;

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        var fxmlLoader = new FXMLLoader(getClass().getResource("/main.fxml"));
        Parent root = fxmlLoader.load();
        stage.setTitle("Example");
        stage.setScene(new Scene(root, APP_WIDTH, APP_HEIGHT));
        stage.show();

        Thread background = new Thread(() -> Platform.runLater(() -> {
            GlobalKeyboardHook keyboardHook = new GlobalKeyboardHook(true);

            keyboardHook.addKeyListener(new GlobalKeyAdapter() {

                @Override
                public void keyPressed(GlobalKeyEvent keyEvent) {
                    System.out.println("Key pressed: " + keyEvent.getVirtualKeyCode());
                }

                @Override
                public void keyReleased(GlobalKeyEvent keyEvent) {
                    System.out.println("Key released: " + keyEvent.toString());
                }
            });
        }));
        background.start();
    }
}
public class Controller implements Initializable {
    @FXML
    private TextField filePath;

    private static String filePathString = "filePathString";

    @Override
    public void initialize(URL url, ResourceBundle resourceBundle) {
        handleFilePath();
    }

    private void handleFilePath() {
        filePath.textProperty().setValue(filePathString);
        filePath.textProperty().addListener(((observable, oldValue, newValue) -> {
            filePath.commitValue();
        }));
    }
}

Solution

The issue is caused by the GlobalKeyboardHook blocking the key events from being propagated to the TextField. To solve this issue, you can add a condition to check if the TextField is focused before processing the key events.

Here’s the modified code that should work:

public class Main extends Application {
    private static final int APP_WIDTH = 400;
    private static final int APP_HEIGHT = 400;

    public static void main(String[] args) {
        SpringApplication.run(Main.class, args);
        launch(args);
    }

    @Override
    public void start(Stage stage) throws Exception {
        var fxmlLoader = new FXMLLoader(getClass().getResource("/main.fxml"));
        Parent root = fxmlLoader.load();
        stage.setTitle("Example");
        stage.setScene(new Scene(root, APP_WIDTH, APP_HEIGHT));
        stage.show();

        Thread background = new Thread(() -> Platform.runLater(() -> {
            GlobalKeyboardHook keyboardHook = new GlobalKeyboardHook(true);

            keyboardHook.addKeyListener(new GlobalKeyAdapter() {

                @Override
                public void keyPressed(GlobalKeyEvent keyEvent) {
                    if (fxmlLoader.getController().filePath.isFocused()) {
                        // do nothing if the TextField is focused
                        return;
                    }
                    System.out.println("Key pressed: " + keyEvent.getVirtualKeyCode());
                }

                @Override
                public void keyReleased(GlobalKeyEvent keyEvent) {
                    if (fxmlLoader.getController().filePath.isFocused()) {
                        // do nothing if the TextField is focused
                        return;
                    }
                    System.out.println("Key released: " + keyEvent.toString());
                }
            });
        }));
        background.start();
    }
}

Note that we’re checking if the TextField is focused using fxmlLoader.getController().filePath.isFocused(). This assumes that the TextField has an fx:id of filePath in the FXML file. If it has a different fx:id, you’ll need to modify this line accordingly.