Python自动化开发:屏蔽用户键鼠输入同时保留pynput热键功能的方法
解决BlockInput屏蔽用户输入时无法用pynput发送热键的问题
BlockInput会全局屏蔽所有输入事件,包括程序模拟的键鼠操作,这就是你遇到问题的核心原因。要实现只屏蔽用户物理输入、允许pynput模拟热键的需求,可以通过Windows的低级钩子(Low-Level Hooks)来精准过滤输入:
核心思路
Windows的低级键盘/鼠标钩子可以捕获系统内所有输入事件,我们可以通过事件的flags字段区分两类输入:
- 用户物理触发的输入,其
flags不会包含LLKHF_INJECTED(键盘)或LLMHF_INJECTED(鼠标)标志 - pynput模拟的输入会自带该标志,只需放行这类事件、拦截物理输入即可
完整代码示例
import ctypes import threading from pynput.keyboard import Key, Controller # 定义钩子所需的Windows结构体 class KBDLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ("vkCode", ctypes.c_uint), ("scanCode", ctypes.c_uint), ("flags", ctypes.c_uint), ("time", ctypes.c_uint), ("dwExtraInfo", ctypes.c_void_p) ] class MSLLHOOKSTRUCT(ctypes.Structure): _fields_ = [ ("pt", ctypes.c_long * 2), ("mouseData", ctypes.c_uint), ("flags", ctypes.c_uint), ("time", ctypes.c_uint), ("dwExtraInfo", ctypes.c_void_p) ] # 键盘钩子回调:拦截物理输入,放行模拟输入 def keyboard_hook_proc(nCode, wParam, lParam): if nCode >= 0: kb_data = ctypes.cast(lParam, ctypes.POINTER(KBDLLHOOKSTRUCT)).contents # LLKHF_INJECTED = 0x10,无此标志则为物理输入 if not (kb_data.flags & 0x10): return 1 # 拦截输入 # 传递其他事件给下一个钩子 return ctypes.windll.user32.CallNextHookEx(0, nCode, wParam, lParam) # 鼠标钩子回调:拦截物理输入,放行模拟输入 def mouse_hook_proc(nCode, wParam, lParam): if nCode >= 0: ms_data = ctypes.cast(lParam, ctypes.POINTER(MSLLHOOKSTRUCT)).contents # LLMHF_INJECTED = 0x10,无此标志则为物理输入 if not (ms_data.flags & 0x10): return 1 # 拦截输入 # 传递其他事件给下一个钩子 return ctypes.windll.user32.CallNextHookEx(0, nCode, wParam, lParam) # 启动钩子并维持消息循环 def start_input_block(): # 设置全局低级键盘钩子 keyboard_hook = ctypes.windll.user32.SetWindowsHookExA( 13, # WH_KEYBOARD_LL keyboard_hook_proc, ctypes.windll.kernel32.GetModuleHandleA(None), 0 ) # 设置全局低级鼠标钩子 mouse_hook = ctypes.windll.user32.SetWindowsHookExA( 14, # WH_MOUSE_LL mouse_hook_proc, ctypes.windll.kernel32.GetModuleHandleA(None), 0 ) # 运行消息循环以维持钩子生效 msg = ctypes.c_int() while ctypes.windll.user32.GetMessageA(ctypes.byref(msg), None, 0, 0) != 0: ctypes.windll.user32.TranslateMessage(ctypes.byref(msg)) ctypes.windll.user32.DispatchMessage(ctypes.byref(msg)) # 任务结束后卸载钩子 ctypes.windll.user32.UnhookWindowsHookEx(keyboard_hook) ctypes.windll.user32.UnhookWindowsHookEx(mouse_hook) # 自动化任务:发送F12热键 def automation_task(): keyboard = Controller() # 等待钩子线程初始化完成 threading.Event().wait(0.5) # 执行热键操作 keyboard.press(Key.f12) keyboard.release(Key.f12) # 发送退出信号终止钩子消息循环 ctypes.windll.user32.PostQuitMessage(0) if __name__ == "__main__": # 启动钩子线程 hook_thread = threading.Thread(target=start_input_block) hook_thread.start() # 启动自动化任务线程 task_thread = threading.Thread(target=automation_task) task_thread.start() # 等待所有线程结束 hook_thread.join() task_thread.join()
注意事项
- 程序需要以管理员权限运行,否则无法成功设置全局低级钩子
- 钩子的消息循环必须持续运行才能维持拦截状态,示例中用线程分离了钩子逻辑和自动化任务
- 若需要长期屏蔽输入,可调整消息循环的退出条件,避免任务结束后立即解除拦截
内容的提问来源于stack exchange,提问作者Eray
相关产品推荐
相关产品推荐

