You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在按键抬起时获取独立于键盘布局的按键编码?

解决方案:基于扫描码识别物理按键实现珀金斯盲文输入

1. 核心数据:硬件扫描码(Scan Code)

键盘的每个物理按键对应唯一的硬件扫描码,它完全独立于系统键盘布局——比如QWERTY布局下的A键,在法语布局(A/Q交换)中物理位置不变,扫描码也保持一致。这正是满足你需求的关键数据。

2. 获取扫描码的实现方式(Windows平台)

通过P/Invoke调用Win32 API捕获低级键盘事件,在按键抬起时提取扫描码,具体步骤如下:

步骤1:定义Win32 API相关结构体与函数

using System.Runtime.InteropServices;
using System.Diagnostics;

[StructLayout(LayoutKind.Sequential)]
public struct KBDLLHOOKSTRUCT
{
    public int vkCode;
    public int scanCode;
    public int flags;
    public int time;
    public IntPtr dwExtraInfo;
}

public delegate IntPtr LowLevelKeyboardProc(int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(int idHook, LowLevelKeyboardProc lpfn, IntPtr hMod, uint dwThreadId);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool UnhookWindowsHookEx(IntPtr hhk);

[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern IntPtr GetModuleHandle(string lpModuleName);

步骤2:设置低级键盘钩子捕获按键抬起事件

private const int WH_KEYBOARD_LL = 13;
private const int WM_KEYUP = 0x0101;
private IntPtr _hookId = IntPtr.Zero;

// 初始化钩子
private void InitKeyboardHook()
{
    using (var curProcess = Process.GetCurrentProcess())
    using (var curModule = curProcess.MainModule)
    {
        _hookId = SetWindowsHookEx(WH_KEYBOARD_LL, KeyboardHookProc, GetModuleHandle(curModule.ModuleName), 0);
    }
}

// 钩子回调函数
private IntPtr KeyboardHookProc(int nCode, IntPtr wParam, IntPtr lParam)
{
    if (nCode >= 0 && wParam == (IntPtr)WM_KEYUP)
    {
        var hookData = (KBDLLHOOKSTRUCT)Marshal.PtrToStructure(lParam, typeof(KBDLLHOOKSTRUCT));
        // 提取当前抬起按键的扫描码
        MapScanCodeToBraille(hookData.scanCode);
    }
    return CallNextHookEx(_hookId, nCode, wParam, lParam);
}

// 程序退出时释放钩子
private void ReleaseHook()
{
    UnhookWindowsHookEx(_hookId);
}

步骤3:扫描码与盲文点的固定映射

预先定义QWERTY目标物理按键对应的扫描码(这些值是硬件层面固定的),直接映射到盲文点:

物理按键(QWERTY布局)扫描码对应盲文点
A0x1E7
S0x1F3
D0x202
F0x211
J0x244
K0x255
L0x266
;(分号)0x278

实现映射逻辑:

// 存储当前按下的盲文点集合
private HashSet<int> _activeBrailleDots = new HashSet<int>();

private void MapScanCodeToBraille(int scanCode)
{
    int? brailleDot = scanCode switch
    {
        0x1E => 7,
        0x1F => 3,
        0x20 => 2,
        0x21 => 1,
        0x24 => 4,
        0x25 => 5,
        0x26 => 6,
        0x27 => 8,
        _ => null
    };

    if (brailleDot.HasValue)
    {
        _activeBrailleDots.Add(brailleDot.Value);
        // 这里可根据业务逻辑判断是否需要生成盲文字符(比如所有按键抬起后)
        GenerateBrailleCharacter();
    }
}

private void GenerateBrailleCharacter()
{
    // 根据_activeBrailleDots中的点,生成对应的Unicode盲文字符
    // 示例逻辑:将点组合转换为盲文编码
    // 最终输出如:\u2800(空白)、\u2801(点1)等
}

3. 关键注意事项

  • 低级键盘钩子是全局生效的,若只需捕获当前应用的输入,可在回调中判断GetForegroundWindow()是否为你的程序窗口。
  • 务必在程序退出时调用ReleaseHook()释放钩子,避免内存泄漏。
  • 扫描码分为普通与扩展类型,上述目标按键均为普通扫描码,无需额外处理扩展标志。

内容的提问来源于stack exchange,提问作者André Polykanine

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.22 03:52:50