文档

后台模式

不抢前台、不移动鼠标,微信最小化也能收发。默认就是这样。

默认即后台

WxParam.BACKGROUND_MODE = True 是出厂值。点击、输入、回车全部走 UIA 模式与 PostMessage —— 不抢前台、不移动鼠标,微信最小化时也照样收发。

动作 后台做法 为什么不用老办法
点控件 PostMessage(WM_LBUTTONDOWN/UP) 投给顶层窗口 Control.Click() 是 SetCursorPos + mouse_event,点在谁身上由 z 序决定
写输入框 ValuePattern.SetValue 不需要键盘焦点
发送 PostMessage(WM_KEYDOWN/UP, VK_RETURN) SendKeys 只送给前台窗口的焦点控件
滚动 PostMessage(WM_MOUSEWHEEL),失败退回 UIA ScrollPattern 老办法依赖鼠标位置

真实鼠标键盘只出现在两条受控路径

WxParam.FOREGROUND_FALLBACK = False        # 默认关闭:不把失败偷偷降级成合成输入
WxParam.ALLOW_FOREGROUND_KEYBOARD = True   # 真实键盘总闸;设 False 即纯后台

库里所有输入都收口在 wx4auto/bginput.py 一层 (bg_type / bg_keys / bg_char / bg_text_chars / bg_clear / bg_wheel / bg_paste / bg_esc)。

它们统一遵守:后台优先 → 失败才降级; 而降级入口 sendkeys_guarded() 会先校验“目标窗口是不是当前前台窗口”, 不是就拒绝执行。这样「绝不把按键发给别的程序」是结构性保证, 不需要每一处调用点都记得。

自己验证

python tools/feature_matrix.py --tier A     # 只读,不发送任何内容

它会在每个检查项前后各量一次鼠标坐标,最后汇总 「✅ 全程鼠标位置未变(免鼠标成立)」。

窗口不见了,库会自己叫回来

微信窗口一旦不可见(最小化、或被 Esc 收进托盘),Qt 就会卸载 UIA Provider —— 之后所有接口都不报错,但全是空结果。所以动手之前先确认窗口可见, 是这个库的硬性前提。

你不需要自己处理:

wx = WeChat()                 # resize=True(默认)→ 自动显示 + 摆到左半屏
wx.ShowWindow()               # 手动:窗口最小化/被隐藏时叫出来(不抢前台)
wx.SnapWindow("left_half")    # left_half / work_area / chat_size / keep
wx.is_visible()               # 现在可见吗

SendMsg(...) / ChatWith(...) 这类操作自带这一步: 发现窗口不可见 → 显示出来 → 按 WINDOW_LAYOUT 摆位 → 等控件树重建 → 才继续做正事。

为什么默认摆到「左半屏」

微信 4.x 只给可见区域注册控件 —— 窗口越高,一次能读到的历史消息越多。 左半屏(宽 = 工作区一半、高 = 工作区全高)是「读得多」和「不挡屏幕」的折中。

from wx4auto import WxParam
WxParam.WINDOW_LAYOUT = "keep"        # 只显示,不动我的窗口位置
WxParam.AUTO_SHOW_WINDOW = False      # 完全不自动显示

不抢前台:显示 / 激活之后,库会把前台还给你原来的窗口。

托盘恢复走的是微信自己的逻辑

被收进托盘后,唯一有效的是点一下任务栏托盘的微信图标。 wx4auto/tray.py 用 UIA 的 LegacyIAccessiblePattern.DoDefaultAction() 实现 —— 不动鼠标,从「被收进托盘」到「控件树恢复」实测约 2 秒。

所以现在这样写就行:

wx.SendMsg("...", who="文件传输助手")   # 窗口在托盘里也照样能发

如果连托盘图标都点不到(系统语言 / 托盘设置特殊),库会明确报错让你手动点, 而不会留下一个白屏窗口。

千万别做的两件事

1. 别用 SW_HIDE / SetWindowPos(SWP_SHOWWINDOW) 摆弄微信窗口

那正是制造白屏死状态的方式。库自身不做任何“隐藏”操作。

2. Esc 会把它藏起来 —— 别再补第二发

微信主窗口收到 Esc 会把自己收进系统托盘:IsWindowVisible 变 0、 句柄还在、进程也活着,然后所有 UIA 调用静默失效。

最典型的翻车方式:关掉浮层(资料卡 / 搜索浮层)之后,又“为了保险”补一发 Esc —— 这一发就落到主窗口上了。

对策(库里已经做好):

  • bg_esc(ctrl, hwnd=None, *, force=False) —— 目标在主窗口里时默认拒绝
  • close() 对 mmui::MainWindow / mmui::ChatPage 直接拒绝

万一真被藏了,不用重启客户端(重启要重新确认登录):

from wx4auto.win32util import EnsureWindowVisible
EnsureWindowVisible(wx.hwnd)     # 最小化 / 被隐藏都能救回来,且不抢前台

朋友圈也是免鼠标的

早先的结论是「朋友圈必须动鼠标」,那是错的。重测后两条路都通:

from wx4auto.bginput import bg_wheel
bg_wheel(lst, -120, times=8)          # 翻页,全程不动鼠标

pyq.get_moments()[0].like()           # 赞
pyq.get_moments()[0].comment("好")     # 评论

翻页失败的真正原因不是“微信不吃后台滚轮”,而是 lParam 的坐标空间用错了:

Windows 消息 lParam 里的坐标
WM_MOUSEMOVE 客户区
WM_LBUTTONDOWN / WM_LBUTTONUP 客户区
WM_MOUSEWHEEL 屏幕 ← 只有它不一样

改为屏幕坐标后,后台滚轮在朋友圈 / 聊天消息 / 会话列表 / 通讯录 四个列表上全部验证通过。

点赞入口 •• 是自绘控件、UIA 里不存在,但它接受后台投递的消息: 悬停 = PostMessage(WM_MOUSEMOVE),点击 = PostMessage(WM_LBUTTONDOWN/UP), 位置是内容格右下角偏移。实测 7/7 条动态都是第一个偏移就命中。