fix: 微信窗口被覆盖时无法唤醒 - 保留PID快速EnumWindows + 冷却缩短到10s

问题根因:
1. UIA (auto.WindowControl) 在窗口被其他程序覆盖时搜索不稳定
2. 缓存HWND验证失败时,PID也被清空,导致每次都要从psutil全进程扫描重新找PID
3. 搜索失败后30s冷却太长

修复:
- 仅清HWND,保留PID供后续EnumWindows快速复用(毫秒级,跳过psutil扫描)
- 新增PID快速路径:验证失败后先用缓存PID直接EnumWindows,再走UIA和psutil兜底
- 冷却时间从30s缩短到10s,加快恢复轮询
This commit is contained in:
youpaishidifu
2026-08-04 14:03:19 +08:00
parent a3b0e35581
commit 597ed7d2fd
2 changed files with 67 additions and 6 deletions
+67 -6
View File
@@ -703,8 +703,9 @@ class MessageMonitor:
except Exception:
pass
# 仅清 HWND,保留 PID 供后续 EnumWindows 快速复用
old_pid = self._cached_pid.get(platform)
self._cached_hwnd.pop(platform, None)
self._cached_pid.pop(platform, None)
# 非阻塞:窗口未找到冷却期内直接跳过搜索,不浪费 12s 超时
cooldown_until = self._window_not_found_until.get(platform, 0)
@@ -712,6 +713,35 @@ class MessageMonitor:
return None
if platform == "wechat":
# ── 快速路径:用缓存 PID 直接 EnumWindows(毫秒级,跳过 psutil 全进程扫描)──
if old_pid:
try:
pid_windows = []
def _fast_enum_cb(h, results, _pid=old_pid):
try:
_, found_pid = win32process.GetWindowThreadProcessId(h)
if found_pid == _pid:
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
if top not in results:
results.append(top)
except Exception:
pass
return True
win32gui.EnumWindows(_fast_enum_cb, pid_windows)
for top in pid_windows:
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
continue
rect = win32gui.GetWindowRect(top)
if (rect[2] - rect[0]) > 300 and (rect[3] - rect[1]) > 200:
self._cached_hwnd[platform] = top
self._window_not_found_until.pop(platform, None)
title = win32gui.GetWindowText(top)
logger.info(f"[{platform}] 窗口已定位(PID 快速): pid={old_pid} title={title!r} top={top}")
return top
except Exception:
pass
# ── UIA 搜索 ──
for name in ["微信", "WeChat"]:
try:
w = auto.WindowControl(Name=name)
@@ -844,8 +874,8 @@ class MessageMonitor:
continue
except Exception:
pass
# 搜索完毕没找到 → 设冷却避免重复 12s 超时
self._window_not_found_until[platform] = time.time() + 30
# 搜索完毕没找到 → 设冷却避免重复 12s 超时(10s 比 30s 更快恢复轮询)
self._window_not_found_until[platform] = time.time() + 10
return None
def _ensure_hwnd_with_timeout(self, platform: str, timeout: float = 12.0) -> Optional[int]:
@@ -875,7 +905,7 @@ class MessageMonitor:
t.start()
if not done.wait(timeout=timeout):
logger.warning(f"[{platform}] _ensure_hwnd 超时({timeout:.0f}s),跳过本轮")
self._window_not_found_until[platform] = time.time() + 30
self._window_not_found_until[platform] = time.time() + 10
return None
return result[0]
@@ -4129,6 +4159,35 @@ class MessageMonitor:
logger.debug(f"[ocr] 过滤器2结果: 未检测到噪声特征,返回False")
return False
# ⭐ OCR噪声前缀清理:去除UI按钮标签等OCR误识别的前缀
# 如"必选,运营方有哪些权利和义务?" → "运营方有哪些权利和义务?"
_OCR_NOISE_PREFIXES = re.compile(
r'^(?:必选|必填|可选|请输入|搜索|查找|查询|发送|取消|确认|确定|提交|保存|删除|编辑| '
r'|添加|新建|创建|设置|修改|返回|关闭|展开|收起|更多|选项|操作|'
r'选择|查看|下载|上传|导入|导出|刷新|重置|清空|复制|粘贴|剪切|'
r'同意|拒绝|同意并|下一步|上一步|完成|跳过|继续|登录|注册|退出|'
r'开启|关闭|启用|禁用|暂停|恢复|开始|停止|运行|'
r'是|否|有|无|对|错|真|假|好|坏|新|旧|已|未)'
r'[,,::\s]*'
)
@staticmethod
def _clean_ocr_prefix(text: str) -> str:
"""清理OCR噪声前缀(UI按钮标签等)。
如"必选,运营方有哪些权利和义务?" → "运营方有哪些权利和义务?"
只清理开头的短前缀(≤6字),避免误删正常消息内容。
"""
if not text or len(text) < 4:
return text
m = MessageMonitor._OCR_NOISE_PREFIXES.match(text)
if m and len(m.group(0)) <= 6:
cleaned = text[len(m.group(0)):].strip()
if cleaned and len(cleaned) >= 2:
logger.debug(f"[ocr] 清理噪声前缀: {text[:30]!r} → {cleaned[:30]!r}")
return cleaned
return text
def _paste_and_send_safe(self, text: str, hwnd: int, msg_hash: int, platform: str) -> bool:
"""Electron 应用安全发送(TIM/微信)。无 AttachThreadInput、无 SendMessage 同步点击。
@@ -5211,14 +5270,16 @@ class MessageMonitor:
self._last_new_msg_time[platform] = time.time()
else:
self._last_new_msg_time[platform] = time.time()
self._last_msg[platform] = item
self._last_msg[platform] = cleaned_item
engine_tag = self._last_chosen_engine.get(platform, "")
logger.info(f"[{platform}] 新消息[{engine_tag}]: {item[:60]}")
# ⭐ 清理OCR噪声前缀(如"必选,")后再搜索知识库
cleaned_item = self._clean_ocr_prefix(item)
# ⚠️ 急停检查:回复前最后检查,防止急停后仍然发送
if not self._running:
return
if self._on_message:
self._on_message(platform, item)
self._on_message(platform, cleaned_item)
if platform == "qq":
if stale == 3:
logger.debug(f"[QQ] 冻结 #{stale},强制刷新")
BIN
View File
Binary file not shown.