You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何区分pynput生成的输入与真实键盘/鼠标输入?实现仅监听真实按键的方案

如何让pynput只监听真实的键盘按键?

我明白你的困扰——用pynput做的监听器会把自己模拟的按键也当成真实输入,导致每次自动按空格时也会打出"Hi",这确实挺烦人的。下面我给你几个可行的方案,优先用pynput实现,实在不行还有其他底层方法:

方案一:用Windows底层标志区分真实/模拟按键(基于pynput)

其实pynput在Windows上是靠系统钩子实现监听的,而系统会给keybd_event生成的模拟按键打一个特殊标记——LLKHF_INJECTED。我们只要在监听器里检查这个标记,就能过滤掉模拟输入。

先给你改好代码:

from pynput.keyboard import Key, Controller, Listener
from time import sleep
import ctypes
from ctypes import wintypes

# 定义Windows钩子需要的事件结构
user32 = ctypes.WinDLL('user32', use_last_error=True)

class KBDLLHOOKSTRUCT(ctypes.Structure):
    _fields_ = [
        ('vkCode', wintypes.DWORD),
        ('scanCode', wintypes.DWORD),
        ('flags', wintypes.DWORD),
        ('time', wintypes.DWORD),
        ('dwExtraInfo', wintypes.LPARAM),
    ]

LLKHF_INJECTED = 0x00000010  # 标记模拟输入的标志位

def onPress(key, event):
    # 解析底层的键盘事件数据
    kb_event = KBDLLHOOKSTRUCT.from_address(event)
    # 只有非模拟的真实按键才触发逻辑
    if not (kb_event.flags & LLKHF_INJECTED):
        if key == Key.space:
            print("Hi (真实按键)")

keyboardController = Controller()
# 开启pass_event参数,让回调能拿到底层事件指针
keyboardListener = Listener(on_press=onPress, pass_event=True)
keyboardListener.start()

while True:
    sleep(5)
    keyboardController.press(Key.space)
    keyboardController.release(Key.space)  # 别忘了释放按键,避免一直按住

这里的核心是给Listener加上pass_event=True,让回调函数能获取到Windows底层的事件指针。然后通过解析事件的flags字段,判断是否是模拟输入——只有没带LLKHF_INJECTED标记的,才是真实的键盘按键。这个方法能准确过滤大多数模拟输入,包括pynput自己生成的。

方案二:临时标记自己的模拟操作(简单但有局限)

如果不想碰底层API,也可以搞个简单的全局开关:在自己模拟按键的时候,告诉监听器这段时间的输入是假的,别处理。

代码大概是这样:

from pynput.keyboard import Key, Controller, Listener
from time import sleep

is_simulating = False

def onPress(key):
    global is_simulating
    # 只有非模拟状态下的按键才触发逻辑
    if not is_simulating and key == Key.space:
        print("Hi (真实按键)")

keyboardController = Controller()
keyboardListener = Listener(on_press=onPress)
keyboardListener.start()

while True:
    sleep(5)
    is_simulating = True
    keyboardController.press(Key.space)
    keyboardController.release(Key.space)
    is_simulating = False

这个方法特别好懂,但缺点也很明显——只能过滤你自己程序生成的模拟按键,如果是其他程序搞的模拟输入,监听器还是会认成真实按键。适合只需要处理自身模拟操作的场景。

方案三:直接用Windows原生钩子(非pynput)

要是pynput的方案满足不了你的需求,那就直接用pywin32调用Windows的低级键盘钩子,完全自己控制监听逻辑:

import win32api
import win32con
import win32gui
from time import sleep
from pynput.keyboard import Controller, Key

user32 = win32api.GetModuleHandle('user32')
LLKHF_INJECTED = 0x10

def low_level_keyboard_proc(nCode, wParam, lParam):
    if nCode >= 0 and wParam == win32con.WM_KEYDOWN:
        # 解析键盘事件结构
        kb_struct = win32api.PyMakeBuffer(lParam, 20)
        vk_code, scan_code, flags, time, extra_info = win32api.UnpackBuffer(kb_struct, 'LLLLL')
        # 过滤模拟输入,只处理真实的空格键按下
        if not (flags & LLKHF_INJECTED) and vk_code == win32con.VK_SPACE:
            print("Hi (真实按键)")
    return win32gui.CallNextHookEx(None, nCode, wParam, lParam)

# 注册低级键盘钩子
hook_id = win32gui.SetWindowsHookEx(win32con.WH_KEYBOARD_LL, low_level_keyboard_proc, user32, 0)

keyboardController = Controller()

try:
    while True:
        sleep(5)
        keyboardController.press(Key.space)
        keyboardController.release(Key.space)
        # 处理系统消息循环,否则钩子会失效
        win32gui.PumpWaitingMessages()
finally:
    # 程序结束时记得卸载钩子
    win32gui.UnhookWindowsHookEx(hook_id)

这个方法完全绕开了pynput,直接和系统底层打交道,过滤逻辑更精准,不过代码也相对复杂一点,需要处理消息循环才能让钩子正常工作。

内容的提问来源于stack exchange,提问作者stackbuar

火山引擎 最新活动