Kristian Kraljić You may compare programming with poetry,
a harmonic script is a piece of creativity which will last forever.
- Kristian Kraljić

Java – Global (low level) Keyboard / Mouse Hook

by Kristian Kraljic, February 9, 2016

About five years ago I started this blog by picking up an idea of Johannes Schüth (Jotschi) about a global keyboard and mouse listener for Java. Today I am very happy to announce the next major version of the library now available on GitHub!

system-hook-logo

Keyboard and mouse events in Java only work, if the registered component is in focus. For example, in case a window looses its focus (e.g. when minimized), it stops receiving any more events. Through a low-level system-wide hook it’s possible to deliver those events regardless. You’ll find all sources of the latest release, as well as binary bundles / prepackaged Java archives (JAR) on the GitHub project page.

The old post describes all changes I did to the initial version by Johannes (Jotschi). For the new major release I again reworked nearly every part of the library, to make it more stable and versatile. Here is what I did:

  • Optimized the native library C code, fixed bugs and removed all parts requireing a C++ compiler in the first place.
  • Renamed the event classes and listener interfaces and prepended Global to avoid conflicts with existing (Swing) event listeners.
  • Added support for mouseWheel events to the GlobalMouseListener.
  • Again allowed negative "out of bounds" values for GlobalMouseEvents, to also track the mouse pointer off screen (e.g. on multi-monitor setups).
  • Improved the threading concept and implemented a native error handling. The GlobalKeyboardHook and GlobalMouseHook constructors will now throw a UnsatisfiedLinkError if the native libraries can not be loaded or a RuntimeException in case hooking fails.
  • All the code has been moved to GitHub and binaries are now beeing continuously built by AppVeyor. Feel free to contribute on GitHub!

(Again) Last but not least, please share your ideas and problems in the comments. I will try to enhance the Global Keyboard / Mouse Hook based on your feedback. For the next release let’s see if we can get Linux and / or Mac OSX support going.

202 Comments

  1. Kristian Kraljic says:

    Hey Rajan,

    please use KeyEvent.VK_RETURN.

    Regards, Kristian

  2. rajan says:

    Kristian…

    thank you sooo much…. it really sorted out my all issue…

  3. Johny says:

    Hello Kristian, i have not much used C++, but have some experience with java. Can you tell me how can i use the example jar which is bundled with dll’s.

    Is it after adding this jar to my build path, i simply implement key-listeners and i would be able to read the output globally?

    can you please give some basic idea as to what are the steps to do a simple test. thank you

  4. Kristian Kraljic says:

    Hey Johny,

    please have a look at my post. I provided a simple code example to implement both, the key- and mouse listener.

    Hope this helps.

    Regards, Kristian

  5. Johny says:

    Hi Kristian,

    thanks it works great. But i think the ascii code returned are not always correct except for alphabets and numbers.

    THanks

  6. punarbhava says:

    Kristian,

    Your example code for the keyboard hook just gives key codes for button presses. How do you get the characters themselves?

  7. Kristian Kraljic says:

    Hello Punarbhava,

    generally you have to map the key-codes to the characters.
    Therefore you could use a simple switch(event.getVirtualKeyCode()) {…}.
    Using the constants KeyEvent.VK_A to KeyEvent.VK_Z you can determine which character was pressed. Don’t forget to evaluate event.isShiftPressed(), so you can determine if an upper- or lowercase character has been typed.

    In future (mid of feburary I think) I plan to include a “toCharacter()” method. So you have the possibility to implement it by yourself or wait for my implementation.

    Hope this helps. Regards,
    Kristian

  8. punarbhava says:

    That helps tremendously Kristian. Thank you. I will implement it myself.

    And thank you for writing this, btw.

  9. punarbhava says:

    Alright, trying to get this working. I can’t pass the string outside of my modification of your example code to use in the rest of my application. Maybe you know how to get this working? My code:


    import de.ksquared.system.keyboard.*;

    public class kb_test
    {
    public static String temp = "";
    public static String output = "";

    public static String main()
    {

    new GlobalKeyListener().addKeyListener(new KeyAdapter()
    {
    @Override public void keyPressed(KeyEvent event)
    {
    switch(event.getVirtualKeyCode())
    {
    case KeyEvent.VK_0:
    if (event.isShiftPressed() == true)
    temp += ")";
    else if (event.isShiftPressed() == false)
    temp += "0";
    break;
    case KeyEvent.VK_1:
    if (event.isShiftPressed() == true)
    temp += "!";
    else if (event.isShiftPressed() == false)
    temp += "1";
    break;
    /* all the rest of the keys go here */
    case KeyEvent.VK_RETURN:
    if (event.isShiftPressed() == true)
    temp += "\n";
    else if (event.isShiftPressed() == false)
    temp += "\n";
    output = temp;
    break;
    } // end switch (event.getVirtualKeyCode())*/
    } // end public void keyPressed
    });
    while(true)
    try
    {
    Thread.sleep(100);
    }
    catch(InterruptedException e)
    {
    e.printStackTrace();
    }
    return output;
    }
    }

    This doesn’t work. It never returns the output.

  10. punarbhava says:

    Er, I know that the return is right now in unreachable code, as in the code never progresses past the while loop. My problem is getting that string from the code– it doesn’t seem to matter where I put the return.

    I tried changing while(true) to use a boolean that is set to be true only after I’ve gotten the input I need (when Enter is pressed), but that didn’t work. I tried putting the return in the block with the stuff for VK_RETURN, that didn’t work either. I don’t know what to do.

  11. Kristian Kraljic says:

    Hello Punarbhava,

    For the whole implementation I used a standard development pattern. So please have a look at the so called Observer Pattern.

    I can’t answer your question, because you will have to understand the basic Java principle of anonymous classes first. The listener class is acting independendly from your main programm in another thread. Therefore you can not use return or return to your code where you added the listener. Try calling a static method, this is not fine programming, but it should do it in your case.

    Please hava a look at: Java classes, anonymous classes, inner classes, encapsulation and threads first.

    This are all Jaca standard techniques.

    Hope this helps. Regards,
    Kristian

  12. punarbhava says:

    Thanks Kristian, I appreciate the prompt response. I’m still relatively new to this stuff, and I’m happy to learn something new. I used the wiki link and a few other sources and I kinda understand observers now, and I have some code that works. However it’s not quite what I want, and I don’t know how to modify it to do exactly what I want it to. I’m going to keep researching, but I’m gonna post the code here to cover all my bases. Here:

    KB_EventSource.java

    Same code I posted last time, except it now extends Observable and implements Runnable. public void main is changed to public void run, and I added
    setChanged();
    notifyObservers(temp);
    to public void KeyPressed.

    KB_RespListener.java

    import java.util.Observable;
    import java.util.Observer;

    public class KB_RespHandler implements Observer
    {
    private String resp;
    public void update (Observable obj, Object arg)
    {
    if (arg instanceof String)
    {
    resp = (String) arg;
    Main.raw_card_data = resp;
    }
    }
    }

    Main.java

    System.out.println("Swipe card >");

    // create an event source - reads from stdin
    final KB_EventSource evSrc = new KB_EventSource();

    // create an observer
    final KB_RespHandler respHandler = new KB_RespHandler();

    // subscribe the observer to the event source
    evSrc.addObserver( respHandler );

    // starts the event thread
    Thread kb_thread = new Thread(evSrc);
    kb_thread.start();
    Thread.sleep(5000);
    System.out.println(raw_card_data);

    This works, as long as I provide the input within 5 seconds. Is there a way I can eliminate that time limit, make it so Main won’t output raw_card_data until the observer is notified of input?