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