windows 键盘挂钩中的ToAscii/ToUnicode会破坏死键

qnzebej0  于 2022-12-19  发布在  Windows
关注(0)|答案(9)|浏览(170)

看起来如果你在一个全局WH_KEYBOARD_LL钩子中调用ToAscii()ToUnicode(),并且一个死键被按下,它将被“销毁”。
例如,假设你在Windows中将输入语言配置为西班牙语,你想在程序中输入一个重音字母á,通常你会按单引号键(死键),然后按字母“a”,然后屏幕上会显示一个重音字母á,正如预期的那样。
但是如果你在一个低级的键盘钩子函数中调用ToAscii()ToUnicode(),这个方法就不起作用了。看起来死键被破坏了,所以没有重音字母á出现在屏幕上。删除对上述函数的调用可以解决这个问题...但是不幸的是,我需要能够调用这些函数。
我在谷歌上搜索了一段时间,虽然很多人似乎都有这个问题,但没有提供好的解决方案。
任何帮助将不胜感激!

**EDIT:**我正在调用ToAscii(),将LowLevelKeyboardProc钩子函数中接收到的虚拟键代码和扫描代码转换为将在屏幕上显示给用户的结果字符。

我尝试了MapVirtualKey(kbHookData->vkCode, 2),但是它的函数不如ToAscii()“完整”;例如,如果你按下Shift + 2,你将得到'2',而不是'@'(或者Shift + 2将为用户的键盘布局/语言产生的任何结果)。
ToAscii()是完美的...直到按下一个死键。

**EDIT 2:**以下是钩子函数,删除了不相关的信息:

LRESULT CALLBACK keyboard_LL_hook_func(int code, WPARAM wParam, LPARAM lParam) {

    LPKBDLLHOOKSTRUCT kbHookData = (LPKBDLLHOOKSTRUCT)lParam;
    BYTE keyboard_state[256];

    if (code < 0) {
        return CallNextHookEx(keyHook, code, wParam, lParam);
    }

    WORD wCharacter = 0;

    GetKeyboardState(&keyboard_state);
    int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                     keyboard_state, &wCharacter, 0);

    /* If ta == -1, a dead-key was pressed. The dead-key will be "destroyed"
     * and you'll no longer be able to create any accented characters. Remove
     * the call to ToAscii() above, and you can then create accented characters. */

    return CallNextHookEx(keyHook, code, wParam, lParam);
}
hzbexzde

hzbexzde1#

很老的一个线程。不幸的是,它不包含我正在寻找的答案,而且没有一个答案看起来工作正常。我最终通过在调用ToUnicode/ToAscii之前检查MapVirtualKey函数的MSB来解决这个问题。看起来工作得很有魅力:

if(!(MapVirtualKey(kbHookData->vkCode, MAPVK_VK_TO_CHAR)>>(sizeof(UINT)*8-1) & 1)) {
    ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
        keyboard_state, &wCharacter, 0);
}

MapVirtualKey的返回值上引用MSDN(如果使用MAPVK_VK_TO_CHAR):
[...]死键(变音符号)通过设置返回值的最高位来指示。[...]

wydwbb8l

wydwbb8l2#

1.停止使用ToAscii()并使用ToUncode()
1.请记住,ToUnicode可能不会返回任何关于死键的信息-这就是为什么它们被称为死键。
1.任何键都将具有扫描码或虚拟键码,但不一定是字符。
您不应该将按钮与字符组合在一起-假设任何键/按钮都有文本表示(Unicode)是错误的。
因此,

  • 对于输入文本,请使用Windows报告的 * 字符
  • 检查按钮按下(如游戏)使用 * 扫描码 * 或 * 虚拟键 *(可能虚拟键更好)。
  • 对于键盘快捷键,请使用 * 虚拟键 * 代码。
fivyi3re

fivyi3re3#

调用'ToAscii'函数两次,以正确处理死键,如:

int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                 keyboard_state, &wCharacter, 0);
int ta = ToAscii((UINT)kbHookData->vkCode, kbHookData->scanCode,
                 keyboard_state, &wCharacter, 0);
If (ta == -1)
 ...
i2byvkas

i2byvkas4#

调用ToAsciiToUnicode两次就是答案,我找到了这个,并将其转换为 Delphi ,它工作了!

cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0);
cnt:=ToUnicode(VirtualKey, KeyStroke, KeyState, chars, 2, 0); //yes call it twice
9vw9lbht

9vw9lbht5#

我遇到了这个问题,而创建一个键盘记录器在C#和以上的答案没有一个为我工作。
经过深入的博客搜索,我偶然发现了这个keyboard listener,它可以完美地处理死键。

gab6jxml

gab6jxml6#

下面是一个完整的代码,涵盖了使用ALT + NUMPAD的死键和快捷键,基本上是一个TextField输入处理的完整实现:

[DllImport("user32.dll")]
    public static extern int ToUnicode(uint virtualKeyCode, uint scanCode, byte[] keyboardState, [Out, MarshalAs(UnmanagedType.LPWStr, SizeConst = 64)] StringBuilder receivingBuffer, int bufferSize, uint flags);

    private StringBuilder _pressCharBuffer = new StringBuilder(256);
    private byte[] _pressCharKeyboardState = new byte[256];

    public bool PreFilterMessage(ref Message m)
    {
        var handled = false;

        if (m.Msg == 0x0100 || m.Msg == 0x0102)
        {

            bool isShiftPressed = (ModifierKeys & Keys.Shift) != 0;
            bool isControlPressed = (ModifierKeys & Keys.Control) != 0;
            bool isAltPressed = (ModifierKeys & Keys.Alt) != 0;
            bool isAltGrPressed = (ModifierKeys & Keys.RMenu) != 0;

            for (int i = 0; i < 256; i++)
                _pressCharKeyboardState[i] = 0;

            if (isShiftPressed)
                _pressCharKeyboardState[(int)Keys.ShiftKey] = 0xff;

            if (isAltGrPressed)
            {
                _pressCharKeyboardState[(int)Keys.ControlKey] = 0xff;
                _pressCharKeyboardState[(int)Keys.Menu] = 0xff;
            }

            if (Control.IsKeyLocked(Keys.CapsLock))
                _pressCharKeyboardState[(int)Keys.CapsLock] = 0xff;

            Char chr = (Char)0;

            int ret = ToUnicode((uint)m.WParam.ToInt32(), 0, _pressCharKeyboardState, _pressCharBuffer, 256, 0);

            if (ret == 0)
                chr = Char.ConvertFromUtf32(m.WParam.ToInt32())[0];
            if (ret == -1)
                ToUnicode((uint)m.WParam.ToInt32(), 0, _pressCharKeyboardState, _pressCharBuffer, 256, 0);
            else if (_pressCharBuffer.Length > 0)
                chr = _pressCharBuffer[0];

            if (m.Msg == 0x0102 && Char.IsWhiteSpace(chr))
                chr = (Char)0;

            if (ret >= 0 && chr > 0)
            {

            //DO YOUR STUFF using either "chr" as special key (UP, DOWN, etc..) 
            //either _pressCharBuffer.ToString()(can contain more than one character if dead key was pressed before)
            //and don't forget to set the "handled" to true, so nobody else can use the message afterwards

            }
        }

        return handled;
    }
1mrurvl1

1mrurvl17#

正是known使得ToUnicode()及其旧版本ToAscii()可以更改当前线程的键盘状态,从而使死键和ALT+NUMPAD键击变得混乱:
当ToUnicodeEx转换虚拟键代码时,它也会更改内核模式键盘缓冲区的状态。此状态更改会影响死键、连字、alt+numpad键输入等。如果与TranslateMessage(它也会更改内核模式键盘缓冲区的状态)一起使用,它还可能导致不希望的副作用。
为了避免这种情况,您可以在单独的线程中执行ToUnicode()调用(它将具有单独的键盘状态),或者在wFlags参数中使用特殊的标志,该标志在ToUnicode() docs中进行了说明:
如果设置了位2,则键盘状态不变(Windows 10,版本1607及更高版本)
或者你可以事先准备好sc-〉charMap表,在语言改变时更新它。
我认为它也可以用在ToAscii()上,但是最好不要用这种依赖于ANSI代码页的老方法。用ToUnicode() API代替,它甚至可以返回ligatures和UTF-16代理对--如果键盘布局有它们的话。Some do
请参见Asynchronous input vs synchronous input, a quick introduction了解其背后的原因。

eoxn13cs

eoxn13cs8#

我将vkCode复制到队列中,然后从另一个线程执行转换

@HOOKPROC
def keyHookKFunc(code,wParam,lParam):
    global gkeyQueue
    gkeyQueue.append((code,wParam,kbd.vkCode))
    return windll.user32.CallNextHookEx(0,code,wParam,lParam)

这样做的好处是不会延迟操作系统对密钥的处理

new9mtju

new9mtju9#

这对我有用

byte[] keyState = new byte[256];

//Remove this if using
//GetKeyboardState(keyState); 

//Add only the Keys you want
keysDown[(int)Keys.ShiftKey] = 0x80; // SHIFT down
keysDown[(int)Keys.Menu] = 0x80; // ALT down
keysDown[(int)Keys.ControlKey] = 0x80; // CONTROL down
  
//ToAscii should work fine         
if (ToAscii(myKeyboardStruct.VirtualKeyCode, myKeyboardStruct.ScanCode, keyState, inBuffer, myKeyboardStruct.Flags) == 1)
{
    //do something
}

相关问题