Friday, September 25, 2009

Global hotkeys for Java applications under Windows OS




Hello all readers.



I would like to present the solution which helps me to realize the working with Global hotkeys in a Java application under Windows OS.


As we remember, Java allows to catch keyboard events for a key or for a key combination, but this works only if the Java application frame or console is active at this moment, but if the user opens or select antoher window, the keyboard events will not reach our Java application. Each application has own event(message) queue and the keyboard events will be sent into this queue unless the application window(console) is active.


To catch global events the program should use special system functions, but Java machine does not provide us this facility by using Java SDK.

So we should develop own solution for managing global hotkeys out of Java sandbox.

My solution is to create a system library (DLL) which can easily calls necessary system functions to listen the global hotkey events. The DLL will be linked up with our Java application with the aid of Java Native Interface (JNI).


The solution which I presented bellow, concerns Windows OS and will not work under Linux.


Let's start!


According to my plan I am going to create the DLL (Dynamic-link library), which will be used by our Java application. The DLL will be initialized by the Java application and the global keyboard events will be managed by the DLL.

The role of Java application is to check the runtime state of the DLL instance periodically. If the hotkey combination was pressed the state of the DLL instance will be changed by setting a special flag.


I used MS Visual Studio 10 and C++ language for developing of the DLL:
on the project page you can find source codes and compiled versions for 32 and 64 bit platforms.
http://code.google.com/p/jkeyboard-hook/


Let's start to  overview my solution from the Java part:

I created the simple testing class for the demonstration of hotkey catching.

We are going to test our keyboard hook on a random hotkey combination like as "CTRL + ALT + H"

You can see that we create GlobalKeyboardHook instance, and specify our hotkey combination with the help of boolean flags and the virtual key value. 
  • KeyEvent.VK_H - Virtual key code for the "H".
  • ALT_Key - if the ALT key is used
  • CTRL_Key - if the CTRL key is used
  • SHIFT_Key - if the SHIFT key is used
  • WIN_Key - if the special "Win" keys are used. These keys have Windows logo.

If the hotkey combination is set, the program can start the hook.

After that the hook will be started, the program waits for the hotkey is pressed to print the "CTRL + ALT + H was pressed" message


com.biletnikov.hotkeys.Main.java:


package com.biletnikov.hotkeys;
import java.awt.event.KeyEvent;
/**
* Testing!
* @author Sergei.Biletnikov
*/
public class Main{

    public static void main(String[] argv) {
        GlobalKeyboardHook hook = new GlobalKeyboardHook();
        // Let me define the following hotkeys: CTRL + ALT + H
        int vitrualKey = KeyEvent.VK_H;
        boolean CTRL_Key = true;
        boolean ALT_Key = true;
        boolean SHIFT_Key = false;
        boolean WIN_Key = false;
        //
        hook.setHotKey(vitrualKey, ALT_Key, CTRL_Key, SHIFT_Key, WIN_Key);
        hook.startHook();
        // waiting for the event
        hook.addGlobalKeyboardListener(new GlobalKeyboardListener() {
            public void onGlobalHotkeysPressed() {
                System.out.println("CTRL + ALT + H was pressed");
            }
        });
        System.out.println("The program waiting for CTRL+ALT+H hotkey...");
    }
}


Nothing special here, it is just a Listener interface.


com.biletnikov.hotkeys.GlobalKeyboardListener.java:


package com.biletnikov.hotkeys;
import java.util.EventListener;
/**
* Hotkeys listener.
* @author Sergei.Biletnikov
*/
public interface GlobalKeyboardListener extends EventListener {
    void onGlobalHotkeysPressed();

}


GlobalKeyboardHook loads DLL that must be placed in the classpath of the Java application, initializes it through the native methods and starts the special thread (DLLStateThread) which checks the state of the DLL instance every 100 ms.

If the hotkey event is occurred the DLLStateThread gets to know about this from the DLL instance by JNI and calls the event listeners.


To link up the DLL with the Java application through JNI, we should generate the header file in C++ which regards to the class that contains native methods (GlobalKeyboardHook).


Compile the java classes and execute: %JAVA_HOME%\bin\javah -jni com.biletnikov.hotkeys.GlobalKeyboardHook to generate the header file: com_biletnikov_hotkeys_GlobalKeyboardHook.h


com.biletnikov.hotkeys.GlobalKeyboardHook.java:


package com.biletnikov.hotkeys;
import java.util.List;
import java.util.ArrayList;
/**
* Global keyboard hook at the Java side.
* @author Sergei.Biletnikov
*/
public class GlobalKeyboardHook {
  
    // ----------- Java Native methods -------------
  
    /**
    * Checks if the hotkeys were pressed.
    * @return true if they were pressed, otherwise false
    */
    public native boolean checkHotKey();
  
    /**
    * Sets the hot key.
    * @param virtualKey Specifies the virtual-key code of the hot key.
    * @param alt Either ALT key must be held down.
    * @param control Either CTRL key must be held down.
    * @param shift Either SHIFT key must be held down.
    * @param win Either WINDOWS key was held down. These keys are labeled with the Microsoft Windows logo.
    * Keyboard shortcuts that involve the WINDOWS key are reserved for use by the operating system.
    * @return If the function succeeds, the return value is TRUE.
    */
    public native boolean setHotKey(int virtualKey, boolean alt, boolean control, boolean shift, boolean win);
  
    /**
    * Resets the installed hotkeys.
    */
    public native void resetHotKey();
    // -------------------------------------
  
    // GlobalKeyboardHook.dll
    private static final String KEYBOARD_HOOOK_DLL_NAME = "GlobalKeyboardHook";
  
    /**
    * For stopping
    */
    private boolean stopFlag;
  
    // -------- Java listeners --------
    private List listeners = new ArrayList();
  
    /**
    * Constructor.
    */
    public GlobalKeyboardHook() {
        // load KeyboardHookDispatcher.dll in the classpath
        System.loadLibrary(KEYBOARD_HOOOK_DLL_NAME);
        System.out.println(KEYBOARD_HOOOK_DLL_NAME + ".dll was loaded");
        stopFlag = false;
    }
  
    public void addGlobalKeyboardListener(GlobalKeyboardListener listener) {
        listeners.add(listener);
    }
  
    public void removeGlobalKeyboardListener(GlobalKeyboardListener listener) {
        listeners.remove(listener);
    }
  
    /**
    * Start the hook. Create and run DLLStateThread thread, for checking the DLL status.
    */
    public void startHook() {
        stopFlag = false;
        DLLStateThread currentWorker = new DLLStateThread();
        Thread statusThread = new Thread(currentWorker);
        statusThread.start();
    }
  
    /**
    * Finish the current KeyboardThreadWorker instance.
    */
    public void stopHook() {
        stopFlag = true;
    }
  
    /**
    * Sends the event notification to all listeners.
    */
    private void fireHotkeysEvent() {
        for (GlobalKeyboardListener listener : listeners) {
            listener.onGlobalHotkeysPressed();
        }
    }
  
    /**
    * This class is base for the thread, which monitors DLL status.
    */
    private class DLLStateThread implements Runnable {
      
        public void run() {
            for(;;) {
                boolean hotKeyPressed = checkHotKey();
                if (hotKeyPressed) {
                    // hot key was pressed, send the event to all listeners
                    fireHotkeysEvent();
                }
                try {
                    Thread.sleep(100); //every 100 ms check the DLL status.
                    // work unless stopFlag == false
                    if (stopFlag) {
                        resetHotKey();
                        break;
                    }
                    } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}


com_biletnikov_hotkeys_GlobalKeyboardHook.h:


/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_biletnikov_hotkeys_GlobalKeyboardHook */

#ifndef _Included_com_biletnikov_hotkeys_GlobalKeyboardHook
#define _Included_com_biletnikov_hotkeys_GlobalKeyboardHook
#ifdef __cplusplus
extern "C" {
    #endif
    /*
    * Class: com_biletnikov_hotkeys_GlobalKeyboardHook
    * Method: checkHotKey
    * Signature: ()Z
    */
    JNIEXPORT jboolean JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_checkHotKey
    (JNIEnv *, jobject);

    /*
    * Class: com_biletnikov_hotkeys_GlobalKeyboardHook
    * Method: setHotKey
    * Signature: (IZZZZ)Z
    */
    JNIEXPORT jboolean JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_setHotKey
    (JNIEnv *, jobject, jint, jboolean, jboolean, jboolean, jboolean);

    /*
    * Class: com_biletnikov_hotkeys_GlobalKeyboardHook
    * Method: resetHotKey
    * Signature: ()V
    */
    JNIEXPORT void JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_resetHotKey
    (JNIEnv *, jobject);

    #ifdef __cplusplus
}
#endif
#endif



Open you IDE for building the DLL. In my case it is Visual Studio 6.0.

Create a DLL project with Microsoft Visual Studio (or other IDE).

Add com_biletnikov_hotkeys_GlobalKeyboardHook.h and all C++ header files which you can find in the %JDK_HOME%\include\ directory.


Create the main C++ file: GlobalKeyboardHook.cpp, with the following content:


GlobalKeyboardHook.cpp:

/*
* Global Keyboard hook
*
*
* JNI Interface for setting a Keyboard Hook and monitoring
* it Java-side
*
*/

#include
#include "windows.h"
#include "Winuser.h"
#include "jni.h"
#include "com_biletnikov_hotkeys_GlobalKeyboardHook.h"

#pragma data_seg(".HOOKDATA") //Shared data among all instances.

static HHOOK hotKeyHook = NULL;
static HANDLE g_hModule = NULL;

const int HOT_KEY_ID = 0xBBBC;
static int hotKeyRegisterStatus = 0;

// Hot key settings
static int keyModifiers = 0;
static int virtualKey = 0;
//
static int hotKeyPressedFlag = 0;

HANDLE messageThreadEvent = NULL;
HANDLE messageThread = NULL;
DWORD messageThreadID;

#pragma data_seg()
#pragma comment(linker, "/SECTION:.HOOKDATA,RWS")
/*
* Class: de_alfah_popup_jni_KeyboardHookThread
* Method: checkHotKey
* Signature: ()Z
*/
JNIEXPORT jboolean JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_checkHotKey
(JNIEnv *env, jobject obj)
{
    int pressedFlag = hotKeyPressedFlag;
    hotKeyPressedFlag = 0;
    return pressedFlag != 0 ? JNI_TRUE : JNI_FALSE;
}

DWORD WINAPI HotKeyListener(LPVOID lpParameter)
{
    hotKeyRegisterStatus = RegisterHotKey(NULL, HOT_KEY_ID, keyModifiers, virtualKey);

    SetEvent(messageThreadEvent);
    if (hotKeyRegisterStatus !=0)
    {
        MSG msg = {0};
        while (GetMessage(&msg, NULL, 0, 0) != 0)
        {
            switch (msg.message)
            {
                case WM_HOTKEY:
                hotKeyPressedFlag = 1;
                break;
            }
        }
    }
    return hotKeyRegisterStatus;;
}

// Reset hot keys by terminating the message thread
static void resetHotKey()
{
    UnregisterHotKey(NULL, HOT_KEY_ID);
    if (messageThread != NULL)
    {
        PostThreadMessage((DWORD) messageThreadID, (UINT) WM_QUIT, 0, 0);
        // TerminateThread (messageThread, 0);
        CloseHandle(messageThread);
        messageThread = NULL;
        messageThreadID = NULL;
    }
    if (messageThreadEvent != NULL) {
        CloseHandle(messageThreadEvent);
        messageThreadEvent = NULL;
    }
    hotKeyRegisterStatus = 0;
}

// Sets new hot key
static int setNewHotKey()
{
    messageThreadEvent = CreateEvent( NULL, TRUE, TRUE, NULL );
    ResetEvent( messageThreadEvent );

    messageThread = CreateThread(NULL, 0, HotKeyListener, 0,0,&messageThreadID);

    WaitForSingleObject( messageThreadEvent, INFINITE );

    return hotKeyRegisterStatus;
}

/*
* Class: de_alfah_popup_jni_KeyboardHookThread
* Method: setHotKey
* Signature: (IZZZZ)Z
*/
JNIEXPORT jboolean JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_setHotKey
(JNIEnv *, jobject, jint vk, jboolean altKey, jboolean ctrlKey, jboolean shiftKey, jboolean winKey)
{
    // define modifiers
    int modifiers = 0;

    if (altKey == JNI_TRUE)
    {
        modifiers = MOD_ALT;
        } if (ctrlKey == JNI_TRUE) {
        modifiers = modifiers | MOD_CONTROL;
        } if (shiftKey == JNI_TRUE) {
        modifiers = modifiers | MOD_SHIFT;
        } if (winKey == JNI_TRUE) {
        modifiers = modifiers | MOD_WIN;
    }
    // set key setings
    keyModifiers = modifiers;
    virtualKey = vk;

    // reset previous hot key
    resetHotKey();
    //
    int status = setNewHotKey();

    return status == 0 ? JNI_FALSE : JNI_TRUE;
}

/*
* Class: de_alfah_popup_jni_KeyboardHookThread
* Method: resetHotKey
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_resetHotKey
(JNIEnv * env, jobject)
{
    resetHotKey();
}

// The main DLL
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved )
{
    switch(ul_reason_for_call)
    {
        case DLL_PROCESS_ATTACH:
        g_hModule = hModule;
        return TRUE;
        case DLL_PROCESS_DETACH:
        resetHotKey();
        return TRUE;
    }
    return TRUE;
}


Compile the DLL.

The GlobalKeyboardHook.dll will appear if all things are fine.


I found two ways how to catch global keyboard events:

  • Using SetWindowsHookEx it is universal hook for many kinds of the events, but it is not handy for catching more than 2 keys combinations
  • RegisterHotKey is a nice function for catching hotkeys.


The DLL use RegisterHotKey function to register a hotkey combination.

The one important aspect is this function sends messages to the message queue of the thread that performed the hotkey registration, therefore we must have a separate thread which fulfills hotkey assigning and listens the message queue.

For each new hotkey combination, the DLL creates the thread which assigns the required hotkey combination for RegisterHotKey and waits for the WM_HOTKEY message.


The current hook DLL was designed to process one hotkey combination. If we set a new combination, the previous thread will be ended and a new one will be created.

Have nice hotkeys :)


43 comments:

  1. Java intorduce the Hotkeys function in the windows and it's such a cool and very helpful hotkey function for the programmers..

    ReplyDelete
  2. This work you made here is really, really good :).
    I was looking for a way similar to jintellitype
    that would not require me to install any special
    software for it to work, and your solution is perfect.

    I had to compile a 64bit version for my 64bit jre
    but that was not a big issue, since you provided
    the source which only relies on standard windows libraries :).

    ReplyDelete
  3. Thank you for your feedback. I'm very glad that this post helped you.

    ReplyDelete
  4. Hi there! Thanks a lot for this amazing overview. The code was really well written too! I was able to use it verbatim in my homebrew project. Hopefully your page sees some more attention!

    ReplyDelete
  5. Excellent Work. Can We also connect two or more hotkeys combination in the similar way?

    ReplyDelete
  6. Thanks. I am going to rework the library to allow multiple hotkey combinations :)

    ReplyDelete
  7. Java development services with dedicated Java developers. Java development outsourcing with proficient Java Developers.

    ReplyDelete
  8. Hi, Thanks for the great tutorial. I successfully created the dll and used it in a Java app. It works fine most of the times. However I would like to mention that sometimes the loading of dll fails with exception "Invalid access to memory location". This randomly inconsistent behaviour should not happen. Please help!!

    ReplyDelete
  9. It is very strange problem which I hear first time.
    Perhaps, it depends on a compile parameters or ... I do not know the cause of the problem, however I can provide you a DLL which is compiled by me.

    ReplyDelete
  10. Any chance you could email me that dll?

    ReplyDelete
  11. Hello Marcus,
    give me your email address please or get it from Gigapeta.

    ReplyDelete
  12. Hello, Sergei. I have been using your dll implementation for the key hook for some time, and I want to say thanks. It works great. I wanted to know if you have created an implementation that allows multiple hotkeys as of yet?

    ReplyDelete
  13. Thanks, unfortunately I have not realized it yet, because I had no time for that :(

    ReplyDelete
  14. Sergei, that's exactly what I was looking for! I'm using it in my game bot to stop process execution (I cannot use mouse because bot is using it in the same time in different window). I really appreciate you released this useful piece of code to the public :) Have a nice day!

    ReplyDelete
  15. I'm a bit new to these things. Can you send me a compiled one. Those link above are dead.
    fantasymagnet@hotmail.com

    ReplyDelete
  16. Ok, I have updated the link to gigapeta.

    ReplyDelete
  17. When I try to run with your dll it says "can't load IA 32-bit .dll on a AMD 64-bit platform". Should i try to make the dll file myself?
    Thankyou

    ReplyDelete
  18. Im already used my own generated .dll but still get the same error. Any clue?

    ReplyDelete
  19. Hello,
    I think it is because of a compiler. That is interesting that 32-bit version of dll is not supported on your 64 platform.
    Sorry, but I am not aware how to solve this problem. Did you try to google this problem?

    ReplyDelete
  20. I installed new windows(32bit) and its worked fine. Thanks a lot.

    ReplyDelete
  21. Hi Sergei,

    Could you please recompile the dll with x64 settings, my Visual Studio 2010 fails to compile your library due to the missing linker references (no idea what it wants).

    Thanks,
    John.

    ReplyDelete
  22. I don't know If I correctly understood it , but I understood that there are 2 ways to use your hot key functions , 1 - compile java classes , generate headers , and then compile a .dll file that will be placed in my java app , and things should work then
    2- the other way is to to get a pre-mad .dll file that was done by someone else , did I understand it correctly ?

    ReplyDelete
  23. Hi Sergei,

    Thank you very much for the useful code. But when I try to run the Main class, I can load the dll file, however I received this error message when it tried to call setHotKey function.
    The output is as follows:
    libGlobalKeyboardHook.dll was loaded
    Exception in thread "main" java.lang.UnsatisfiedLinkError: com.biletnikov.hotkeys.GlobalKeyboardHook.setHotKey(IZZZZ)Z
    at com.biletnikov.hotkeys.GlobalKeyboardHook.setHotKey(Native Method)
    at com.biletnikov.hotkeys.Main.main(Main.java:24)
    So far I follow all the steps and everything worked fine, except when I try to run the Main class.

    ReplyDelete
    Replies
    1. Hello Yee Mei Lim,
      please read this article:
      http://javarevisited.blogspot.com/2012/03/javalangunsatisfiedlinkerror-no-dll-in.html
      perhaps it can help you.

      Delete
    2. Hi Yee Mei Lim,
      Were you able to solve your problem? I'm having exactly the same issue and the link provided by Sergei isn't giving any good results to solve my problem!
      Thanks

      Delete
  24. Hi Sergei
    Thanks for the Awesome Solution,It's a life saver

    I only have one problem,I'm not able to catch the printscreen event,

    Please help,I require this desperately.

    Thanks a lot in Advance

    ReplyDelete
    Replies
    1. windows doesnt send a WM_KEYDOWN event for VK_SNAPSHOT and so you need to recompile the C++ library to use WM_KEYUP instead

      Delete
  25. Can we register only numpad enter key?
    Can we perform action only on keypad enter button and not on the other regular enter button?

    ReplyDelete
    Replies
    1. I think, no, it is not possible in the current version.

      Delete
  26. Dear Sir,

    When i am going to compile java code javac Main.java
    It gives an error

    GlobalKeyboardHook.java:85: error: incompatible types
    for (GlobalKeyboardListener listener : listeners) {
    ^
    required: GlobalKeyboardListener
    found: Object
    Note: .\GlobalKeyboardHook.java uses unchecked or unsafe operations.
    Note: Recompile with -Xlint:unchecked for details.

    ReplyDelete
    Replies
    1. Please, get the code and libraries here :
      https://code.google.com/p/jkeyboard-hook/

      Delete
  27. Hello all, I'm back. Today, I have recompiled DLL on Visual Studio 2010 :) My previous build was done on MS Visiaul Studio 6 , too old.
    Also, I noticed that I placed here Debug version of DLL , it includes some additional things like source code, full variables names and etc, now I produced releases versions and the file size decreased to 8-9 kb :)
    Since 2009, lots of PC has 64 bit architecture.
    Currently I see 2 ways :
    - have 2 DLL version JKeyboardHook32.dll and JKeyboardHook64.dll , I think the most easiest and reasonable!
    - use special wrapper tool to wrap 32 bit version

    ReplyDelete
  28. I have initiated the project on the google code platform:
    http://code.google.com/p/jkeyboard-hook/

    Now, it is possible to see the latest source code for MS Visual Studio 2010
    and you can download compiled DLLs:
    http://code.google.com/p/jkeyboard-hook/downloads/list

    If I have enough time, I will place more info there.

    ReplyDelete
  29. I was getting java.lang.UnsatisfiedLinkError: quickymote.GlobalKeyboardHook.setHotKey(IZZZZ)Z like someone else was getting.

    I found the solution by inspecting the sources the dll:
    It says: JNIEXPORT jboolean JNICALL Java_com_biletnikov_hotkeys_GlobalKeyboardHook_setHotKey

    What I did to get it to work was to create a project named Java (in Eclipse), and make a package called com.biletnikov.hotkeys, and put all the files in that package.

    In other words, if you want to have another structure of your project you need to recompile the dll for youself.

    To Sergei: Do you know if or when you will add support for more than one key binding? Would be awesome if you did!

    ReplyDelete
  30. Hello, thanks for that nice work, it is wonderful.

    I have just one question, is it possible to register Keylistener without catching the event?

    Example: Register Listener for Ctr + V, disable the possibility to paste. Maybe it would be possible to use a Boolean in constructor to configure these behavior?
    So the program recognize the Hotkey, but let the system paste its stuff.

    ReplyDelete
  31. Hi,
    if I want to compile this I got an error:

    JKeyboardHook64.dll was loaded
    The program waiting for CTRL+ALT+H hotkey...
    Exception in thread "Thread-0" java.lang.Error: Unresolved compilation problem:
    Type mismatch: cannot convert from element type Object to GlobalKeyboardListener

    at com.biletnikov.hotkeys.GlobalKeyboardHook.fireHotkeysEvent(GlobalKeyboardHook.java:88)
    at com.biletnikov.hotkeys.GlobalKeyboardHook.access$0(GlobalKeyboardHook.java:87)
    at com.biletnikov.hotkeys.GlobalKeyboardHook$DLLStateThread.run(GlobalKeyboardHook.java:103)
    at java.lang.Thread.run(Unknown Source)


    I hope you could help me ;)

    ReplyDelete
    Replies
    1. try changing line 46 in GlobalKeyboardHook.java
      from
      private List listeners = new ArrayList();
      to
      private List listeners = new ArrayList();

      Delete
    2. I mean to
      private List listeners = new ArrayList();

      Delete
    3. wth? it changes my code, anyways add after List and ArrayList

      Delete
  32. Rize is expertised in different technologies has been generated in the course of delivering many Java applications for our clients in different parts of the world.

    Java Development

    ReplyDelete
  33. hey nice source for us, thanks for sharing this information with and you have done incredible work to collecting information that i have need.

    Java Development Company

    ReplyDelete