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

如何在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脚本这类自定义发送进程,最可靠的方案是:

  1. 修改AHK脚本,在发送按键前通过文件、管道或网络端口发送一条包含自身进程名的消息
  2. 让Python程序监听这个消息通道,实现100%准确的发送进程识别

备注:内容来源于stack exchange,提问作者martinotecco

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.15 03:23:22