Search code examples
javac#jnakeymapping

Why keyboard callback work twice and make double key printing (Java/JNA)?


Solved. See my answer below.


In my code I'm trying to remap keyboad keys, e.g. z -> s. I'm using JNA library 5.6.0 and jna-platform 5.6.0, but people who familiar with C languages can understand me too because JNA is using WinAPI functions from User32 and Kernel32 dll's.

My problem is that the newkey is printing twice and between newkeys the oldkey appears. When I type 'z' it should print 's', but it's printing 'szs'.

My code:

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;

public class ReMapper {

    private static WinUser.HHOOK hHook;
    final User32 user32Library = User32.INSTANCE;
    WinDef.HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
    static WinUser.INPUT input = new WinUser.INPUT();

    public void reMapOn() {
        WinUser.LowLevelKeyboardProc keyboardHook = new WinUser.LowLevelKeyboardProc() {

            @Override
            public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wParam, WinUser.KBDLLHOOKSTRUCT kbdllhookstruct) {
                if (nCode >= 0) {
                    switch (wParam.intValue()) {
                        case WinUser.WM_KEYUP:
                        case WinUser.WM_KEYDOWN:
                        case WinUser.WM_SYSKEYUP:
                        case WinUser.WM_SYSKEYDOWN:
                            if (kbdllhookstruct.vkCode == 90) {
                                sendKey(83);
                                break;
                            }
                        default:
                            wParam.setValue(WinUser.WM_SYSKEYDOWN);
                            break;
                    }
                }
                Pointer ptr = kbdllhookstruct.getPointer();
                long peer = Pointer.nativeValue(ptr);
                return user32Library.CallNextHookEx(hHook, nCode, wParam, new WinDef.LPARAM(peer));
            }
        };

        hHook = user32Library.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, keyboardHook, hMod, 0);

        int result;
        WinUser.MSG msg = new WinUser.MSG();
        while ((result = user32Library.GetMessage(msg, null, 0, 0)) != 0) {
            if (result == -1) {
                break;
            } else {
                user32Library.TranslateMessage(msg);
                user32Library.DispatchMessage(msg);
            }
        }
    }

    static void sendKey(int keyCode) {

        input.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
        input.input.setType("ki"); // Because setting INPUT_INPUT_KEYBOARD is not enough: https://groups.google.com/d/msg/jna-users/NDBGwC1VZbU/cjYCQ1CjBwAJ
        input.input.ki.wScan = new WinDef.WORD(0);
        input.input.ki.time = new WinDef.DWORD(0);
        input.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
// Press
        input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
        input.input.ki.dwFlags = new WinDef.DWORD(0);  // keydown

        User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());

// Release
        input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
        input.input.ki.dwFlags = new WinDef.DWORD(2);  // keyup

        User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
    }

    public static void main(String[] args) {
        new ReMapper().reMapOn();
    }
}

My printing example:

enter image description here

Can anyone help me to understand why is it happening?


Solution

  • Solved by changing returnig value of native callback() function. After remaping oldKey to newKey we should explicitly return new WinDef.LRESULT(1);

    if (wParam.intValue() == WinUser.WM_KEYDOWN) {
        if (kbDllHookStruct.vkCode == 90) {
         sendKey(83);
         return new WinDef.LRESULT(1);
        }
    }
    

    Here is the working code after all my changes that remaps 'z' to 's':

    import com.sun.jna.Pointer;
    import com.sun.jna.platform.win32.*;
    
    public class ReMapper {
    
        private static WinUser.HHOOK hHook;
        final User32 user32Library = User32.INSTANCE;
        WinDef.HMODULE hMod = Kernel32.INSTANCE.GetModuleHandle(null);
        static WinUser.INPUT input = new WinUser.INPUT();
    
        public void reMapOn() {
            WinUser.LowLevelKeyboardProc keyboardHook = new WinUser.LowLevelKeyboardProc() {
    
                @Override
                public WinDef.LRESULT callback(int nCode, WinDef.WPARAM wParam, WinUser.KBDLLHOOKSTRUCT kbDllHookStruct) {
                    if (nCode >= 0) {
                        if (wParam.intValue() == WinUser.WM_KEYDOWN) {
                            if (kbDllHookStruct.vkCode == 90) {
                                sendKey(83);
                                return new WinDef.LRESULT(1);
                            }
                        }
                    }
                    Pointer ptr = kbDllHookStruct.getPointer();
                    long peer = Pointer.nativeValue(ptr);
                    return user32Library.CallNextHookEx(hHook, nCode, wParam, new WinDef.LPARAM(peer));
                }
            };
    
            hHook = user32Library.SetWindowsHookEx(WinUser.WH_KEYBOARD_LL, keyboardHook, hMod, 0);
    
            int result;
            WinUser.MSG msg = new WinUser.MSG();
            while ((result = user32Library.GetMessage(msg, null, 0, 0)) != 0) {
                if (result == -1) {
                    break;
                } else {
                    user32Library.TranslateMessage(msg);
                    user32Library.DispatchMessage(msg);
                }
            }
        }
    
        static void sendKey(int keyCode) {
    
            input.type = new WinDef.DWORD(WinUser.INPUT.INPUT_KEYBOARD);
            input.input.setType("ki"); // Because setting INPUT_INPUT_KEYBOARD is not enough: https://groups.google.com/d/msg/jna-users/NDBGwC1VZbU/cjYCQ1CjBwAJ
            input.input.ki.wScan = new WinDef.WORD(0);
            input.input.ki.time = new WinDef.DWORD(0);
            input.input.ki.dwExtraInfo = new BaseTSD.ULONG_PTR(0);
    // Press
            input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
            input.input.ki.dwFlags = new WinDef.DWORD(0);  // keydown
    
            User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
    
    // Release
            input.input.ki.wVk = new WinDef.WORD(keyCode); // 0x41
            input.input.ki.dwFlags = new WinDef.DWORD(2);  // keyup
    
            User32.INSTANCE.SendInput(new WinDef.DWORD(1), (WinUser.INPUT[]) input.toArray(1), input.size());
        }
    
        public static void main(String[] args) {
            new ReMapper().reMapOn();
        }
    }
    

    Now it works like a charm :)