修复QQ读不到消息+微信前台判定失败(窗口在前台仍报错)\n\n问题1: QQ(TIM)读不到消息\n 根因: _focus_message_area在窗口未前置前SendKeys({Esc})发给错误窗口\n 然后_read_via_clipboard_once只用{Esc}未能聚焦消息显示区\n TIM中{Esc}只退出搜索到会话列表,非消息区域\n 修复: 删_focus_message_area(整个方法窗口未前置时无意义)\n _read_via_clipboard_once中TIM路径改为:\n {Esc}{Esc}退出搜索→{Down}选中对话→{Enter}打开(加载消息区)\n 此时Ctrl+A选中消息内容而非会话列表\n\n问题2: 微信已前置仍报目标窗口不在前台\n 根因: _cached_hwnd存储UIA NativeWindowHandle(可能是子窗口句柄)\n 而GetForegroundWindow()返回顶层窗口句柄→hwnd不匹配\n 修复: _ensure_hwnd存储时加GetAncestor(hwnd,GA_ROOT)确保存顶层句柄\n _ensure_foreground改用root_hwnd判断\n 加SwitchToThisWindow fallback(权限要求比SetForegroundWindow低)\n \n其他: .gitignore加tim_dump.txt

This commit is contained in:
youpaishidifu
2026-07-25 15:02:26 +08:00
parent 0322e2af64
commit 6a96111db6
2 changed files with 36 additions and 67 deletions
+1
View File
@@ -25,6 +25,7 @@ local_config.json
# Debug dumps
.debug/
tim_dump.txt
# Logs
*.log
+35 -67
View File
@@ -138,9 +138,11 @@ class MessageMonitor:
rect = w.BoundingRectangle
if rect.width() > 200 and rect.height() > 100:
h = w.NativeWindowHandle
self._cached_hwnd[platform] = h
logger.info(f"[{platform}] 窗口已定位: {name} handle={h}")
return h
# 确保存顶层窗口句柄(与 GetForegroundWindow 返回值一致)
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
self._cached_hwnd[platform] = top
logger.info(f"[{platform}] 窗口已定位: {name} handle={top}")
return top
except Exception:
pass
elif platform == "qq":
@@ -151,10 +153,11 @@ class MessageMonitor:
rect = w.BoundingRectangle
if rect.width() > 200 and rect.height() > 100:
h = w.NativeWindowHandle
self._cached_hwnd[platform] = h
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
self._cached_hwnd[platform] = top
self._app_variant[platform] = "qq_nt" if name == "QQ" else "tim"
logger.info(f"[{platform}] 窗口已定位(名称): {name}")
return h
logger.info(f"[{platform}] 窗口已定位(名称): {name} top={top}")
return top
except Exception:
pass
# QQ NT/TIM → TXGuiFoundation 类名
@@ -166,12 +169,12 @@ class MessageMonitor:
rect = child.BoundingRectangle
if rect.width() > 200 and rect.height() > 100:
h = child.NativeWindowHandle
# 判断变体:名称含"TIM"则为tim,否则qq_nt
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
cname = str(child.Name or "")
self._app_variant[platform] = "tim" if "TIM" in cname.upper() else "qq_nt"
self._cached_hwnd[platform] = h
logger.info(f"[{platform}] 窗口已定位(TXGuiFoundation): {child.Name}")
return h
self._cached_hwnd[platform] = top
logger.info(f"[{platform}] 窗口已定位(TXGuiFoundation): {child.Name} top={top}")
return top
except Exception:
pass
except Exception:
@@ -234,66 +237,27 @@ class MessageMonitor:
except Exception as e:
logger.debug(f"[{platform}] 滚动到底失败: {e}")
def _focus_message_area(self, platform: str) -> bool:
"""聚焦消息区:Esc清弹窗 → 点击会话列表选对话 → 点击消息区聚焦。
两步点击确保焦点落在消息显示区域而非联系人/会话列表。
SendMessage 同步发送(Electron 响应同步消息)。
不移动物理鼠标。
"""
hwnd = self._cached_hwnd.get(platform)
if not hwnd:
return False
try:
rect = win32gui.GetWindowRect(hwnd)
if not rect:
return False
left, top, right, bottom = rect
w = right - left
h = bottom - top
if w < 100 or h < 100:
return False
# 先从前景(_read_via_clipboard_once 已做,但再确保)
# Esc 退出搜索/弹出框,恢复默认焦点
auto.SendKeys('{Esc}')
time.sleep(0.15)
auto.SendKeys('{Esc}')
time.sleep(0.15)
# 1. 点击会话列表(左 ~15%)选中一个对话
x1 = int(w * 0.15)
y1 = int(h * 0.35)
lp1 = win32api.MAKELONG(x1, y1)
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lp1)
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONUP, 0, lp1)
time.sleep(0.3)
# 2. 点击消息显示区(中右 ~60%)聚焦消息列表
x2 = int(w * 0.60)
y2 = int(h * 0.40)
lp2 = win32api.MAKELONG(x2, y2)
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONDOWN, win32con.MK_LBUTTON, lp2)
win32gui.SendMessage(hwnd, win32con.WM_LBUTTONUP, 0, lp2)
time.sleep(0.3)
logger.debug(f"[{platform}] 已聚焦消息区 (conv={x1},{y1} msg={x2},{y2})")
return True
except Exception as e:
logger.debug(f"[{platform}] 聚焦消息区失败: {e}")
return False
def _ensure_foreground(self, hwnd, max_retries=3) -> bool:
"""确保窗口在前台,重试多次。失败返回 False,不强行发送。"""
# 确保用顶层窗口句柄(与 GetForegroundWindow 返回值一致)
root_hwnd = win32gui.GetAncestor(hwnd, win32con.GA_ROOT)
for i in range(max_retries):
self._attach_set_foreground(hwnd)
self._attach_set_foreground(root_hwnd)
time.sleep(0.2)
try:
fg = win32gui.GetForegroundWindow()
if fg == hwnd:
if fg == root_hwnd:
return True
# 目标窗口是前台窗口的子窗口也可
if fg and win32gui.IsChild(hwnd, fg):
if fg and win32gui.IsChild(root_hwnd, fg):
return True
except Exception:
pass
# fallback: SwitchToThisWindow(比 SetForegroundWindow 权限要求低)
try:
ctypes.windll.user32.SwitchToThisWindow(root_hwnd, True)
time.sleep(0.15)
fg = win32gui.GetForegroundWindow()
if fg == root_hwnd or (fg and win32gui.IsChild(root_hwnd, fg)):
return True
except Exception:
pass
@@ -388,13 +352,18 @@ class MessageMonitor:
if not self._ensure_foreground(hwnd, max_retries=2):
return []
# TIM/QQ NT: Esc 退出搜索/弹框,确保焦点在消息区域
# TIM: 键盘导航到消息区域
variant = self._app_variant.get(platform, "")
if variant == "tim":
auto.SendKeys('{Esc}')
time.sleep(0.15)
time.sleep(0.1)
auto.SendKeys('{Esc}')
time.sleep(0.1)
# 确保选中一个对话并打开(加载右侧消息)
auto.SendKeys('{Down}')
time.sleep(0.15)
auto.SendKeys('{Enter}')
time.sleep(0.5) # 等消息加载
# Ctrl+A (全选)
auto.SendKeys('{Ctrl}a')
@@ -590,8 +559,7 @@ class MessageMonitor:
texts = self._read_message_list(window)
if texts:
return texts
# TIM / QQ NT UIA 缓存失效: 聚焦消息区 → 剪贴板
self._focus_message_area(platform)
# TIM / QQ NT UIA 缓存失效: 剪贴板法(内部含前置+键盘聚焦)
texts = self._read_via_clipboard(platform)
if texts:
return texts