2

Is it possible to implement jnativehook with the javafx method .setOnKeyPressed() ?

For example this is how it listens to key presses while the application is focused:

ListView<String> list = new ListView<String>();

list.setOnKeyPressed(new EventHandler<KeyEvent>() {

    @override
    public void handle(KeyEvent event){

        if (event.getCode() == KeyCode.U) {

            //Go Up list..

         }

         event.consume();

     }

});

But i want it to listen to global key presses while the focus is on another application, so ideally i want it to look like this:

list.setOnKeyPressed(new EventHandler<NativeKeyEvent>() {

    @override
    public void handle(NativeKeyEvent event){

        if (event.getKeyCode() == NativeKeyEvent.VC_U) {

            //Go Up list..

         }

         event.consume();

     }

});

However the first line flags the errors:

1. The method setOnKeyPressed(EventHandler <? super KeyEvent>) in the type Node is not applicable for the arguments (new EventHandler<NativeKeyEvent>(){})

2. Bound mismatch: The type NativeKeyEvent is not a valid substitute for the bounded parameter <T extends Event> of the type EventHandler<T>

I'm sure i need to overload the method to take into account these parameters however i am not sure how to go about it and how to deal with the second error.

Also the method .consume() is unsupported for jnativehook.

Or is there another way to go up and down a listview in javafx without using these methods and is compatible with jnativehook?

themaal
  • 21
  • 3
  • 1
    Why would you want to do this? Imagine your user is using your app, and receives an email that needs an immediate reply. Do you really want your app to do stuff in response to arbitrary key presses they make while - just for example - typing an email? – James_D Feb 23 '18 at 15:32
  • Its not really for users, i need it for a research project where i run a simulator and this application side by side so i need the key presses to be registered by this application while the user goes up and down a list and makes selections. So i only need to register 3 keys, a key that makes it go up the list, another key that makes it go down the list and another to make a selection. – themaal Feb 23 '18 at 15:41
  • 1
    There is Robot, and the java.awt key events. Stealing key presses from an other application would be a serious security flaw. Things like Selenium exist that do remote control. So you either have to wrap the simulator, which is when it is a java app relatively easy. – Joop Eggen Feb 23 '18 at 15:51
  • The experiment will be done in a controlled environment, where only two applications will be running, the simulator and this custom app. Whatever keys i decide to use for traversing the list, i will make sure they will be binded in the simulator to do nothing. I don't really understand what you mean by 'stealing' keys, think of it more like using the input for two applications at the same time. – themaal Feb 23 '18 at 16:35

2 Answers2

1

First, let's talk about event delivery and UI threading as it is important that you understand the context switching that is going behind the scenes with the threads and the memory being shared between them.

Java like just about every other application ever designed uses a single thread for all UI events. The reason for this has been well documented all over the internet. So if you are using AWT/Swing or Java FX, your events are dispatched on the AWT/Swing or Java FX platform thread respectively and no other threads should be interacting or expecting these events.

JNativeHook uses its own dispatch thread that is completely independent of Java's UI. This was primarily done for compatibility reasons, as I did not want to force a particular UI API on the implementing user. Because of this, if you would like to use AWT/Swing, Java FX or something else, you must dispatch the native event on the thread context that is responsible for your UI. There is a spring dispatcher built into the library, and a Java FX dispatcher I have posted here that allow for very simple context switching via the default dispatch method by registering the dispatcher with the GlobalScreen.

You maybe asking yourself, "what if I want to modify the event before it is dispatched?" This should also be possible, but requires a little more effort that registering a dispatch class. You should be able to extend the GlobalScreen class and its anonymous inner classes to accomplish anything else you need. For example, if you want to dispatch Swing InputEvents instead of NativeInputEvents you could do the following:

public class CustomScreen extends GlobalScreen {

    // Static blocks are run when the class is loaded.
    static {
        // Add adaptors to the listener to convert the events.
        addNativeKeyListener(new KeyAdapter());
        addNativeMouseListener(new MouseAdapter());

        setEventDispatcher(new SwingDispatchService());
    }

    private static class KeyAdapter extends SwingKeyAdapter {
        public void keyTyped(KeyEvent keyEvent) {
            KeyListener[] listeners = eventListeners.getListeners(KeyListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].keyTyped(keyEvent);
            }
        }

        public void keyPressed(KeyEvent keyEvent) {
            KeyListener[] listeners = eventListeners.getListeners(KeyListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].keyPressed(keyEvent);
            }
        }

        public void keyReleased(KeyEvent keyEvent) {
            KeyListener[] listeners = eventListeners.getListeners(KeyListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].keyReleased(keyEvent);
            }
        }
    }

    private static class MouseAdapter extends SwingMouseAdapter {
        public void mouseClicked(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseClicked(mouseEvent);
            }
        }

        public void mousePressed(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mousePressed(mouseEvent);
            }
        }

        public void mouseReleased(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseReleased(mouseEvent);
            }
        }

        public void mouseEntered(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseEntered(mouseEvent);
            }
        }

        public void mouseExited(MouseEvent mouseEvent) {
            MouseListener[] listeners = eventListeners.getListeners(MouseListener.class);

            for (int i = 0; i < listeners.length; i++) {
                listeners[i].mouseExited(mouseEvent);
            }
        }
    }

    public static void addSwingKeyListener(KeyListener listener) {
        if (listener != null) {
            eventListeners.add(KeyListener.class, listener);
        }
    }

    public static void removeKeyListener(KeyListener listener) {
        if (listener != null) {
            eventListeners.remove(KeyListener.class, listener);
        }
    }

    public static void addMouseListener(MouseListener listener) {
        if (listener != null) {
            eventListeners.add(MouseListener.class, listener);
        }
    }

    public static void removeMouseListener(MouseListener listener) {
        if (listener != null) {
            eventListeners.remove(MouseListener.class, listener);
        }
    }
}

All this does is register a couple adaptor classes included with the library with the parent global screen and dispatches to the swing listeners in the adaptors. There are other ways to do this that may be more powerful like extending the GlobalScreen.NativeHookThread anonymous inner class, but that will take significantly more code on your part.

Dealing with Java FX is very similar to swing, you can find an example dispatcher at my previous post: https://stackoverflow.com/a/45423466/773849

As far as the .consume() goes, yes that is unsupported but still possible on some platforms. If you are using Windows or OS X, you can stop event propagation with some clever code as outlined in the wiki. This will require that you override the aforementioned NativeHookThread inner class and you must be fast with your events as the NativeHookThread is not really a not a thread in this case and will block the operating system. This method is not available on Linux, because linux has no way to perform this function at this point in time.

Alex Barker
  • 4,316
  • 4
  • 28
  • 47
1

Try with addEventFilter to the Scene:

@Override
public void start(Stage stage) throws Exception {
    VBox root = new VBox();
    Scene scene = new Scene(root);
    scene.addEventFilter(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
        System.out.println("Key pressed");
        if (event.getCode() == KeyCode.F1) {
            System.out.println("F1 pressed");
        }
        event.consume();
    });
    stage.setScene(scene);
    stage.show();
}
victorio
  • 6,224
  • 24
  • 77
  • 113