Приложение Swing Thread заблокировано JNA

У меня есть приложение Swing в Windows 10. Также я добавил хук низкоуровневой клавиатуры, который должен перехватывать события клавиатуры и переназначать, например, кнопку «z» на кнопку «s».

Это необходимо для перехвата событий клавиатуры вне моего Java-приложения. Я реализовал его, используя версию JNA 5.6.0 и версию jna-platform 5.6.0. Он отлично работает, но не внутри моего приложения Swing.

Моя проблема в том, что когда хук включен, приложение Swing заблокировано. Я не могу нажать на любую кнопку J и даже закрыть Jframe.

Я предполагаю, что это как-то связано с потоками, но я очень слаб в потоках и многопоточности.

Воспроизводимый пример.

Класс ТестФраме:

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);
    }
}

Класс переназначения:

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());
    }
}

Вот экран с заблокированным Jframe после нажатия кнопки «Нажмите здесь»:

Класс ReMapper отлично работает отдельно от приложения Swing.

reMapOn() позволяет переназначить «z» на «s». Но мне нужно, чтобы он работал внутри моего приложения Swing, а не блокировал его.

Может кто знает в чем может быть проблема и как ее исправить?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
102
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Глядя на ваш код reMapOn, вы видите, что он имеет цикл while, и это указывает на то, что он может работать бесконечно и блокировать пользовательский интерфейс приложений.

Что вам нужно сделать, так это просто в вашем методе addActionListener вызвать метод reMapOn в отдельном потоке. Это можно сделать с помощью простого Thread или Swing Worker:

Пример Swing Worker (предпочтительное решение, поскольку вы можете переопределить done и при необходимости манипулировать компонентами Swing в этом методе после завершения переназначения):

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?");

Пример темы:

new Thread(() -> {
    ReMapper reMapper = new ReMapper();
    reMapper.reMapOn();
}).start();
textField.setText("Is my frame locked?");

Некоторые другие моменты:

  1. Не используйте null/AbsoluteLayout, а используйте соответствующий LayoutManager
  2. Не вызывайте setBounds() или setSize() для компонентов, если вы используете правильный менеджер компоновки, это будет обработано за вас.
  3. Вызовите JFrame#pack(), прежде чем сделать рамку видимой при использовании LayoutManager
  4. Не расширяйте класс JFrame без необходимости
  5. Все компоненты Swing должны вызываться в EDT через SwingUtilities.invokeLater

Спасибо за помощь. Теперь это работает как шарм :)

Gepard 12.12.2020 18:03

Другие вопросы по теме