Reputation: 67
I have Swing application on Windows 10. Also I added Low Level Keyboard hook which should intercept keyboard events and remap for example 'z' to 's' button.
This is needed to hook keyboard events outside my java application. I have implemented it using JNA version 5.6.0 and jna-platform version 5.6.0. It works fine but not inside my Swing application.
My problem is when the hook is ON, swing application a kind of locked. I can not press to any Jbutton and even close Jframe at all.
My guess is that it has something to do with threads, but I'm very weak at threads and multithreading.
Reproducible example.
TestFrame class:
import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class TestFrame extends JFrame {
public static void main(String [] args) {
TestFrame frame = new TestFrame();
JTextField textField=new JTextField();
textField.setBounds(50,50, 150,20);
JButton button=new JButton("Click Here");
button.setBounds(50,100,95,30);
button.addActionListener(new ActionListener(){
public void actionPerformed(ActionEvent e){
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
textField.setText("Is my frame locked?");
}
});
frame.add(button);
frame.add(textField);
frame.setSize(400,400);
frame.setLayout(null);
frame.setVisible(true);
}
}
ReMapper class:
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) { // 90 is key code = z
sendKey(83); // 83 is key code = s
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());
}
}
Here is the screen with locked Jframe after clicking 'Click Here' button:
ReMapper class works fine separately from Swing application.
reMapOn()
allows to remap 'z' to 's'. But I need it to work inside my Swing app and not block it..
Does anyone know what the problem might be and how to fix it?
Upvotes: 1
Views: 139
Reputation: 36423
Looking at your reMapOn
code, it has a while loop and that points to the fact that it could run indefinitely and block the applications UI.
What you need to do is simply in your addActionListener
method call the reMapOn
method on its own thread. This can be done either using a simple Thread or Swing Worker:
Swing Worker example (preferred solution as you can override done
and manipulate swing components if needed in that method when the remapper had ended):
new SwingWorker<Void, Void>() {
@Override
protected Void doInBackground() throws Exception {
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
return null;
}
}.execute();
textField.setText("Is my frame locked?");
Thread example:
new Thread(() -> {
ReMapper reMapper = new ReMapper();
reMapper.reMapOn();
}).start();
textField.setText("Is my frame locked?");
Some other points are:
null
/AbsoluteLayout
rather use an appropriate LayoutManagersetBounds()
or setSize()
on components, if you use a correct layout manager this will be handled for youJFrame#pack()
before setting the frame to visible when using a LayoutManager
JFrame
class unnecessarilySwingUtilities.invokeLater
Upvotes: 2