Skip to content

物理输入监控 (PIM)

PIM(Physical Input Monitor)提供人机协同保护机制——当检测到用户正在进行物理键盘/鼠标操作时,自动暂停自动化操作,避免冲突。

基本用法

python
from pywxauto import Weixin

# idle_wait=3: 等待用户停止物理输入 3 秒后再执行操作
wx = Weixin(idle_wait=3)
wx.send_text("张三", "hello")  # 自动等待物理空闲

工作原理

  1. 通过 Windows 低级钩子(SetWindowsHookExW)监控物理键盘和鼠标事件
  2. 排除程序自身注入的虚拟输入(通过 LLKHF_INJECTED / LLMHF_INJECTED 标志判断)
  3. 所有带 @PIM.guard 装饰器的方法在执行前,会等待物理输入空闲达到指定秒数

上下文管理器

python
from pywxauto import PIM, Weixin

with PIM(idle_wait=3):
    wx = Weixin()
    wx.send_text("张三", "hello")

手动管理

python
from pywxauto import PIM

pim = PIM(idle_wait=3)
if not pim.start():
    print("钩子安装失败,物理输入监控未启用")

# ... 执行操作 ...

pim.stop()

start() 会等待钩子安装完成并返回是否成功。钩子装不上(权限不足、被安全软件拦截等)时会输出警告并关闭监控,@PIM.guard 随之停止等待 —— 不会出现"以为有保护其实没有"的情况。print(PIM()) 可以查看钩子状态:

<PIM(idle_wait=3, lock_input=False, status=running, kb=True, mouse=True) at 0x...>

锁定物理输入

在操作期间锁定用户的键盘鼠标(需要管理员权限):

python
wx = Weixin(idle_wait=3, lock_input=True)
# 每次操作时会先锁定输入,操作完成后解锁

手动等待空闲

python
from pywxauto import PIM

PIM.start()

# 等待物理空闲 5 秒
PIM.wait_for_idle(5.0)

# 获取当前空闲时长
idle = PIM.get_idle_duration()
print(f"已空闲 {idle:.1f} 秒")

@PIM.guard 装饰器

所有 pywxauto 的 UI 操作方法都带有 @PIM.guard 装饰器。你也可以在自己的代码中使用:

python
from pywxauto import PIM

@PIM.guard
def my_operation():
    # 等待 PIM.idle_wait 秒物理空闲
    ...

等待秒数和是否锁定输入都读全局配置(PIM.idle_wait / PIM.lock_input),装饰器没有逐方法开关。想在某个函数里临时多等一会儿,直接调 PIM.wait_for_idle(秒数)

python
def my_critical_operation():
    PIM.wait_for_idle(5)
    ...

监控必须已启动,否则等待会被跳过(会输出一条警告):

python
PIM.start()          # 或 Weixin(idle_wait=...) 间接启动
my_operation()

生成器函数不能用这个装饰器 —— 装饰器只在创建迭代器时执行,等待时机不对。改在函数体开头调用 PIM.ensure_idle()

python
def iter_items():
    PIM.ensure_idle()        # 等同于 @PIM.guard
    ...
    yield item

注意事项

  • PIM 仅在 idle_wait > 0 时自动启动;只在装饰器上写秒数不会自动启动监控
  • 钩子安装失败会警告并关闭监控,不会静默降级为空保护
  • PIM 是全局单例,所有状态存储在类变量上
  • lock_input 需要管理员权限,否则 BlockInput 调用会静默失败
  • block_input() / unblock_input() 带引用计数 + 线程归属,嵌套 guard 不会提前解锁, 多线程下也不会出现解锁失败导致键鼠锁死
  • 监控线程以 daemon 模式运行,主进程退出时自动结束