如何在Python中检测触发按键事件的具体源进程
如何在Python中检测触发按键事件的具体源进程
我可以明确告诉你:物理按键的源可以准确识别,但虚拟按键(由其他进程发送,比如AHK脚本)的发送进程无法通过常规API直接获取,这是由Windows系统的输入机制决定的,下面详细解释并给出可行的改进方案:
为什么你的现有代码无法满足需求?
你当前的代码是通过获取前台窗口的进程ID来判断源,但这个思路的本质错误在于:
- 物理按键的目标是前台窗口,此时前台窗口进程和按键源无关
- 虚拟按键(比如AHK脚本用
SendInput发送)是被注入到系统全局输入队列,最终派送到前台窗口的消息队列,所以你拿到的是接收按键的进程,而非发送按键的进程
核心限制与可行区分方案
1. 物理键盘按键:可准确识别
物理按键触发的事件不会带有系统注入标记,我们可以通过低级键盘钩子直接判断。
2. 虚拟按键:无法直接获取发送进程
Windows系统本身不会记录发送虚拟按键的进程ID——因为SendInput这类注入API是将事件添加到全局输入队列,而非直接关联到发送进程。只有两种特殊情况可以间接追踪:
- 发送虚拟按键的进程仍在运行(比如循环发送按键的AHK脚本):可以通过枚举活跃进程结合启动时间、窗口信息推测,但无法100%准确
- 你能控制发送进程:比如自己编写的AHK脚本,可以通过共享内存、管道等主动通知Python程序,实现准确识别
改进后的Python代码
下面的代码可以准确区分物理/虚拟按键,同时优化原有的进程获取逻辑:
import ctypes import ctypes.wintypes as wintypes import psutil from pynput.keyboard import Listener as KeyboardListener # 定义Windows API相关结构与常量 user32 = ctypes.WinDLL('user32', use_last_error=True) kernel32 = ctypes.WinDLL('kernel32', use_last_error=True) WH_KEYBOARD_LL = 13 WM_KEYDOWN = 0x0100 WM_SYSKEYDOWN = 0x0104 LLKHF_INJECTED = 0x10 # 虚拟按键的注入标记 # 低级键盘钩子的事件结构体 class KBDLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ('vkCode', wintypes.DWORD), ('scanCode', wintypes.DWORD), ('flags', wintypes.DWORD), ('time', wintypes.DWORD), ('dwExtraInfo', wintypes.LPARAM) ] def is_injected_keypress(): """判断当前按键是否为虚拟注入的按键""" result = False def hook_callback(nCode, wParam, lParam): nonlocal result if nCode >= 0 and (wParam == WM_KEYDOWN or wParam == WM_SYSKEYDOWN): kbd_data = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents if kbd_data.flags & LLKHF_INJECTED: result = True return user32.CallNextHookEx(None, nCode, wParam, lParam) # 注册临时低级钩子并触发一次消息循环 hook_proc = ctypes.CFUNCTYPE(wintypes.LRESULT, wintypes.INT, wintypes.WPARAM, wintypes.LPARAM)(hook_callback) hook_handle = user32.SetWindowsHookExW(WH_KEYBOARD_LL, hook_proc, kernel32.GetModuleHandleW(None), 0) msg = wintypes.MSG() user32.PeekMessageW(ctypes.byref(msg), None, 0, 0, 1) user32.UnhookWindowsHookEx(hook_handle) return result def get_active_process_name(): """获取当前前台窗口的进程名""" hwnd = user32.GetForegroundWindow() if not hwnd: return "未知进程" pid = wintypes.DWORD() user32.GetWindowThreadProcessId(hwnd, ctypes.byref(pid)) if pid.value == 0: return "未知进程" try: return psutil.Process(pid.value).name() except (psutil.NoSuchProcess, psutil.AccessDenied): return "无权限访问进程" def identify_keypress_source(): if is_injected_keypress(): process_name = get_active_process_name() return f"虚拟按键(接收进程:{process_name})- 无法直接获取发送进程" else: return "物理键盘" def on_press(key): try: source = identify_keypress_source() if hasattr(key, 'char') and key.char: print(f"按键 '{key.char}' 来自 {source}") else: print(f"特殊按键 {key} 来自 {source}") except Exception as e: print(f"处理按键时出错:{e}") if __name__ == "__main__": print("启动按键监听... 按Ctrl+C停止") with KeyboardListener(on_press=on_press) as listener: listener.join()
额外建议
如果你的核心需求是追踪AHK脚本这类自定义发送进程,最可靠的方案是:
- 修改AHK脚本,在发送按键前通过文件、管道或网络端口发送一条包含自身进程名的消息
- 让Python程序监听这个消息通道,实现100%准确的发送进程识别
备注:内容来源于stack exchange,提问作者martinotecco
相关产品推荐
相关产品推荐

