Alex
Alex

Reputation: 15353

Listening for input without focus in Java

I'm making a small program in Java using the Robot class. The program takes over the mouse. while in the course of debugging if it starts acting in a way that I don't want it's hard to quit the program, since I can't move the mouse over to the terminate button in eclipse, and I can't use hotkeys to hit it because the mouse is constant clicking in another window, giving that window focus instead.

What I'd like to do is just hook up a keylistener so that when I hit q I can quit the program, but the only way I know how to do this involves making a window, and that window needs focus to capture the input. Is there a way to listen for keyboard or mouse input from anywhere, regardless of what has focus?

Upvotes: 25

Views: 44167

Answers (7)

Fer-jg
Fer-jg

Reputation: 41

(As mentioned by @MasterID and shown on JNativeHook's documentation for native keyboard input detection {main GitHub project here}),

This code should be enough to listen to any key without app focus (press and/or release):

>>Remember to add the jnativehook library in your project to be able to use all its utilities.<<

public class yourClass implements NativeKeyListener {//<-- Remember to add the jnativehook library

public void nativeKeyPressed(NativeKeyEvent e) {
    System.out.println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
}

public void nativeKeyReleased(NativeKeyEvent e) {
        System.out.println("Key Released: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
}

public void nativeKeyTyped(NativeKeyEvent e) {
        System.out.println("Key Typed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
}

public static void main(String args[]){
    //Just put this into your main:
    try {
        GlobalScreen.registerNativeHook();
    }
    catch (NativeHookException ex) {
        System.err.println("There was a problem registering the native hook.");
        System.err.println(ex.getMessage());
        System.exit(1);
    }
    
    GlobalScreen.addNativeKeyListener(new yourClass());
    //Remember to include this^                     ^- Your class
}
}

For this particular problem, use the nativeKeyPressed method like this:

public void nativeKeyPressed(NativeKeyEvent e) {
    System.out.println("Key Pressed: " + NativeKeyEvent.getKeyText(e.getKeyCode()));
    if (e.getKeyCode() == NativeKeyEvent.VC_Q){
        System.exit(1);
    }
}

Note that JNativeHook by default shows a lot of stuff in your console that you might not want, to change that, just add this right before the try-catch that you used in the main function as shown (this is also going to turn off warning and error messages, more info here):

//(From here)
Logger logger = Logger.getLogger(GlobalScreen.class.getPackage().getName());
logger.setLevel(Level.OFF);
logger.setUseParentHandlers(false);
//(To there-^)
try {
    GlobalScreen.registerNativeHook();
}
catch (NativeHookException ex) {
    System.err.println("There was a problem registering the native hook.");
    System.err.println(ex.getMessage());
    System.exit(1);
}

Disclaimer: I know this question was solved years ago, I just hope someone finds this a little easier to find/use.

Upvotes: 2

MasterID
MasterID

Reputation: 1510

There is a library that does the hard work for you: https://github.com/kwhat/jnativehook

Upvotes: 36

durron597
durron597

Reputation: 32343

Here's a pure Java way to do it to solve the problem you've described (not the KeyListener problem... the quit test early when using robot problem):

Throughout your test, compare the mouse position with one that your test has recently set it to. If it doesn't match, quit the test. Note: the important part of this code is the testPosition method. Here's code that I used recently:

public void testSomething() throws Exception {
    try {
        // snip

        // you can even extract this into a method "clickAndTest" or something
        robot.mouseMove(x2, y2);
        click();
        testPosition(x2, y2);

        // snip
    } catch (ExitEarlyException e) {
        // handle early exit
    }
}

private static void click() throws InterruptedException {
    r.mousePress(InputEvent.BUTTON1_DOWN_MASK);
    Thread.sleep(30 + rand.nextInt(50));
    r.mouseRelease(InputEvent.BUTTON1_DOWN_MASK);
    Thread.sleep(30 + rand.nextInt(50));
}

private static void testPosition(int x2, int y2) throws ExitEarlyException {
    Point p = MouseInfo.getPointerInfo().getLocation();
    if(p.x != x2 || p.y != y2) throw new ExitEarlyException();
}

Upvotes: 1

ldog
ldog

Reputation: 12161

This is not a trivial problem and Java doesn't give you a way to do it elegantly. You can use a solution like banjollity suggested but even that won't work all the time if your errant mouse clicks open another fullsized window currently open in your taskbar for example.

The fact is, Java by default gives developers very little control over the OS. This is due to 2 main reasons: security (as citied by java documentation) and the fact that different operating systems handle events completely differently and making one unified model to represent all of these would probably not make a whole lot of sense.

So to answer your question, I imagine what you want is some kind of behaviour for your program where it listens for keypresses globally, not just in your application. Something like this will require that you access the functionality offered by your OS of choice, and to access it in Java you are going to need to do it through a Java Native Interface (JNI) layer.

So what you want to do is:

  1. Implement a program in C that will listen for global keypresses on your OS, if this OS is Windows than look for documentation on windows hooks which is well docuemented by Microsoft and MSDN on the web and other places. If your OS is Linux or Mac OS X then you will need to listen for global keypresses using the X11 development libraries. This can be done on an ubunutu linux distro according to a Howto that I wrote at http://ubuntuforums.org/showthread.php?t=864566

  2. Hook up your C code to your Java code through JNI. This step is actually the easier step. Follow the procedure that I use in my tutorial at http://ubuntuforums.org/showthread.php?t=864566 under both windows and linux as the procedure for hooking up your C code to your Java code will be identical on both OSes.

The important thing to remember is that its much easier to get your JNI code working if you first code and debug your C/C++ code and make sure that it is working. Then integrating it with Java is easy.

Upvotes: 14

user467317
user467317

Reputation: 21

Had same problem. In my case, robot just controlled a single Windows App, that was maximized. I placed these lines at top of main loop driving the robot:

Color iconCenterColor = new Color(255,0,0); // if program icon is red

if (iconCenterColor.equals(robot.getPixelColor(10,15))) throw new IllegalStateException("robot not interacting with the right app.");

To cancel the robot, just alt-tab to another app. Works great for a simple one app driving robot.

Upvotes: 2

banjollity
banjollity

Reputation: 4540

Have your program open a second window which displays underneath your main window but is maximised, then your errant mouse clicks will all be received by the maximised window, and it can receive your keyboard input.

Upvotes: 0

Glenn
Glenn

Reputation: 6573

Start the program from a command line in a terminal and use Ctrl-C to terminate it.

Upvotes: 0

Related Questions