diff --git a/.gitignore b/.gitignore index f94d0c8..ef2a243 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ local_config.json # Debug dumps .debug/ +tim_dump.txt # Logs *.log diff --git a/monitor.py b/monitor.py index 08a06f5..e0e1bca 100644 --- a/monitor.py +++ b/monitor.py @@ -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