修复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:
@@ -25,6 +25,7 @@ local_config.json
|
||||
|
||||
# Debug dumps
|
||||
.debug/
|
||||
tim_dump.txt
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
+35
-67
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user