后台模式
不抢前台、不移动鼠标,微信最小化也能收发。默认就是这样。
默认即后台
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 条动态都是第一个偏移就命中。