GetAsyncKeyState и VirtualKeys/специальные символы с использованием JNA (JAVA)

Я работаю над двухсторонним приватным чатом, который будет работать в полноэкранном режиме.

Это необходимо для того, чтобы пользователь вводил полупрозрачное текстовое поле в верхней части экрана , даже если оно не имеет фокуса.

Используя следующий код, , я могу обнаружить ВСЕ физические клавиши, но с виртуальными клавишами сложное время.

SHIFT.

2.

Однако Shift + 2 обнаруживаются как отдельные клавиши (даже если [SHIFT+2] дает @ на моей клавиатуре). IE: программа выводит как SHIFT, так и 2, но не то, что они производят: @.

Проблема в том, как я буду конвертировать в символ в зависимости от клавиатуры? Например:

  • На британской клавиатуре SHIFT + 2 даст мне " (кавычки).
  • На американской клавиатуре SHIFT +2 даст мне @.

Как я могу преобразовать конкретный символ в зависимости от клавиатуры?

Вот код:

static interface User32 extends Library {
    public static User32 INSTANCE = (User32) Native.loadLibrary("User32", User32.class);

    short GetAsyncKeyState(int key);
    short GetKeyState(int key);

    IntByReference GetKeyboardLayout(int dwLayout);
    int MapVirtualKeyExW (int uCode, int nMapType, IntByReference dwhkl);

    boolean GetKeyboardState(byte[] lpKeyState);

    int ToUnicodeEx(int wVirtKey, int wScanCode, byte[] lpKeyState, char[] pwszBuff, int cchBuff, int wFlags, IntByReference dwhkl);

}



public static void main(String[] args)  {   
    long currTime = System.currentTimeMillis();

    while (System.currentTimeMillis() < currTime + 20000)
    {
        for (int key = 1; key < 256; key++)
            {
                if (isKeyPressed(key)) 
                    getKeyType(key);
            }
    }
}



private static boolean isKeyPressed(int key)
{
    return User32.INSTANCE.GetAsyncKeyState(key) == -32767;
}



private static void getKeyType(int key)
{

    boolean isDownShift = (User32.INSTANCE.GetKeyState(VK_SHIFT) & 0x80) == 0x80;
    boolean isDownCapsLock = (User32.INSTANCE.GetKeyState(VK_CAPS)) != 0;


    byte[] keystate = new byte[256];
    User32.INSTANCE.GetKeyboardState(keystate); 


    IntByReference keyblayoutID = User32.INSTANCE.GetKeyboardLayout(0);
    int ScanCode  = User32.INSTANCE.MapVirtualKeyExW(key, MAPVK_VK_TO_VSC, keyblayoutID);






    char[] buff = new char[10];

    int bufflen = buff.length;
    int ret = User32.INSTANCE.ToUnicodeEx(key, ScanCode, keystate, buff, bufflen, 0, keyblayoutID);


    switch (ret)
    {
        case -1: 
            System.out.println("Error");
        break;

        case 0:  // no translation

        break;

        default: 
        System.out.println("output=" + String.valueOf(buff).substring(0, ret));
    }




}

Он отлично работает и выводит нажатые клавиши, но не работает с комбинациями Shift+. Я понимаю, что я могу сделать "Switch" и сменить Shift + 3 на "E", но это не будет работать с разными клавиатурами.

Ответ 1

Я понял. После многих, многих и многих часов поиска мне удалось создать метод, который преобразует комбинацию в то, что она должна быть на текущей раскладке клавиатуры. Он не имеет дело с мертвыми ключами (например, акцентами), но он ловит все [SHIFT+Combinations], которые мне нужно, чтобы поймать.

Чтобы использовать его, вызовите его следующим образом:

getCharacter(int vkCode, boolean shiftKeyPressed);

Итак, смотри эту магию. Если я хочу получить то, что SHIFT+3 даст мне на клавиатуре (£), я использую:

getCharacter(KeyEvent.VK_3, true);

Вот код:

public static char getCharacter(int vkCode, boolean shiftKeyPressed)
{

    byte[] keyStates = new byte[256]; //Create a keyboard map of 256 keys

    if (shiftKeyPressed)
    {
        keyStates[16]=-127; //Emulate the shift key being held down
        keyStates[160]=-128; //This needs to be set as well
    }

    IntByReference keyblayoutID = User32.INSTANCE.GetKeyboardLayout(0); //Load local keyboard layout

    int ScanCode  = User32.INSTANCE.MapVirtualKeyExW(vkCode, MAPVK_VK_TO_VSC, keyblayoutID); //Get the scancode

    char[] buff = new char[1];

    int ret = User32.INSTANCE.ToUnicodeEx(vkCode, ScanCode, keyStates, buff, 1, 0, _currentInputLocaleIdentifier);

    switch (ret)
    {
    case -1: //Error
        return (char) -1;

    case 0:  //No Translation
        return (char) 0;

    default: //Returning key...
        return buff[0];
    }
}

Вот объявления:

final static int MAPVK_VK_TO_VSC = 0;
static IntByReference _currentInputLocaleIdentifier; 

static interface User32 extends Library {

    public static User32 INSTANCE = (User32) Native.loadLibrary("User32", User32.class);


    IntByReference GetKeyboardLayout(int dwLayout);
    int MapVirtualKeyExW (int uCode, int nMapType, IntByReference dwhkl);

    boolean GetKeyboardState(byte[] lpKeyState);

    int ToUnicodeEx(int wVirtKey, int wScanCode, byte[] lpKeyState, char[] pwszBuff, int cchBuff, int wFlags, IntByReference dwhkl);

}

Большое спасибо BrendanMcK, который помог мне добраться до этого решения.

Ответ 2

Попробуйте вместо этого использовать JIntelliType. Его гораздо проще использовать, чем JNA, и он должен иметь возможность делать SHIFT + ключ (MOD_SHIFT). Единственная проблема, с которой вы можете столкнуться, - это обнаружить 3, но это легко решить (например, с помощью кода печати KeyListener ключа).

Ответ 3

GetKeyboardState имеет некоторые проблемы, но GetAsyncKeyState работает нормально.

Здесь приведен полный рабочий пример консольного приложения, которое считывает состояние клавиатуры из любого окна. Протестировано с двумя раскладками клавиатуры без клавиатуры в Windows 7.

Обрабатывает все =) и, в частности, комбинации SHIFT + (т.е. SHIFT + 3 будет переведен на правильный символ для текущей раскладки клавиатуры)

P.S. David, thanx к вашему примеру кода, я наконец выяснил правильные параметры для функций MapVirtualKeyExW и ToUnicodeEx:)

P.P.S. Код находится в С#, но я думаю, его можно легко портировать на Java (так как, когда я читаю ваш код, я ошибочно принял его на С# и только намного позже заметил "JAVA" в заголовке вопроса)

using System;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Forms;

namespace KeyboardInputTest
{
    class Program
    {
        static void Main(string[] args)
        {
            new KeyboardTestClass().RunTest();
        }
    }

    public class KeyboardTestClass
    {
        public void RunTest()
        {
            while (true)
            {
                string keyString = string.Empty;
                if (ReadKeyboardInput(ref keyString) && keyString.Length > 0)
                {
                    Console.WriteLine(string.Format("Pressed: {0}", keyString));
                }
                Thread.Sleep(10);
            }
        }

        public bool ReadKeyboardInput(ref string res)
        {
            var hwnd = WinAPI.GetForegroundWindow();
            var pid = WinAPI.GetWindowThreadProcessId(hwnd, IntPtr.Zero);
            var keyboardLayoutHandle = WinAPI.GetKeyboardLayout(pid);

            foreach (var key in (Keys[])Enum.GetValues(typeof(Keys)))
            {
                if (Keyboard.GetAsyncKeyState(key) == -32767)
                {
                    switch (key)
                    {
                        // handle exceptional cases
                        case Keys.Enter:
                        case Keys.LineFeed:
                            res = string.Empty;
                            return false;
                    }
                    res = ConvertVirtualKeyToUnicode(key, keyboardLayoutHandle, Keyboard.ShiftKey);
                    return true;
                }
            }
            return false;
        }

        public string ConvertVirtualKeyToUnicode(Keys key, IntPtr keyboardLayoutHandle, bool shiftPressed)
        {
            var scanCodeEx = Keyboard.MapVirtualKeyExW(key, VirtualKeyMapType.ToVScanCodeEx, keyboardLayoutHandle);
            if (scanCodeEx > 0)
            {
                byte[] lpKeyState = new byte[256];
                if (shiftPressed)
                {
                    lpKeyState[(int)Keys.ShiftKey] = 0x80;
                    lpKeyState[(int)Keys.LShiftKey] = 0x80;
                }
                var sb = new StringBuilder(5);
                var rc = Keyboard.ToUnicodeEx(key, scanCodeEx, lpKeyState, sb, sb.Capacity, 0, keyboardLayoutHandle);
                if (rc > 0)
                {
                    return sb.ToString();
                }
                else
                {
                    // It a dead key; let flush out whats stored in the keyboard state.
                    rc = Keyboard.ToUnicodeEx(key, scanCodeEx, lpKeyState, sb, sb.Capacity, 0, keyboardLayoutHandle);
                    return string.Empty;
                }
            }
            return string.Empty;
        }
    }

    // Win API Imports:
    public enum VirtualKeyMapType : int
    {
        ToChar = 2,
        ToVScanCode = 0,
        ToVScanCodeEx = 4
    }
    public static class Keyboard
    {
        public static bool ShiftKey
        {
            get
            {
                return Convert.ToBoolean((int)GetAsyncKeyState(Keys.ShiftKey) & 32768);
            }
        }

        [DllImport("User32.dll")]
        public static extern short GetAsyncKeyState(Keys vKey);

        [DllImport("user32.dll", CharSet = CharSet.Unicode, EntryPoint = "MapVirtualKeyExW", ExactSpelling = true)]
        public static extern uint MapVirtualKeyExW(Keys uCode, VirtualKeyMapType uMapType, IntPtr dwKeyboardLayoutHandle);

        [DllImport("user32.dll", CharSet = CharSet.Unicode, ExactSpelling = true)]
        public static extern int ToUnicodeEx(Keys wVirtKey, uint wScanCode, byte[] lpKeyState, StringBuilder pwszBuff, int cchBuff, uint wFlags, IntPtr dwKeyboardLayoutHandle);
    }

    public class WinAPI
    {
        [DllImport("user32.dll")]
        public static extern IntPtr GetForegroundWindow();

        [DllImport("user32")]
        public static extern int GetWindowThreadProcessId(IntPtr hwnd, IntPtr lpdwProcessId);

        [DllImport("user32")]
        public static extern IntPtr GetKeyboardLayout(int dwLayout);
    }
}