From 9cd1e607aa2902bad6c50968fa933d31d9629186 Mon Sep 17 00:00:00 2001 From: youpaishidifu Date: Sat, 25 Jul 2026 17:21:16 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AETIM=E5=85=A8=E5=B1=80keybd=5Fevent?= =?UTF-8?q?=E8=AF=AF=E6=9D=80=EF=BC=9A=E5=88=A0Esc=E9=94=AE=E7=9B=98?= =?UTF-8?q?=E5=AF=BC=E8=88=AA+PostMessage=E6=9B=BF=E4=BB=A3+=E6=81=A2?= =?UTF-8?q?=E5=A4=8DUIA=E5=A4=9A=E5=B1=82=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- monitor.py | 86 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 28 deletions(-) diff --git a/monitor.py b/monitor.py index 805923b..54f34b7 100644 --- a/monitor.py +++ b/monitor.py @@ -400,34 +400,17 @@ class MessageMonitor: if not self._ensure_foreground(hwnd, max_retries=2): return [] - # 注意:不再执行 {Esc}{Esc}{Down}{Enter} 键盘导航,避免每次 poll - # 切换到不同会话导致读到不同消息集。改用 Esc→Esc→Enter - # 回到会话列表后重新打开当前(同一)对话,确保消息区可读。 - variant = self._app_variant.get(platform, "") - if variant in ("tim", "qq_nt"): - VK_ESC = 0x1B - VK_RETURN = 0x0D - VK_END = 0x23 - VK_CONTROL = 0x11 - # 取消弹窗/菜单,回到会话列表 - self._send_key_press(VK_ESC, 0.08) - self._send_key_press(VK_ESC, 0.08) - # 重新打开当前选中的对话(同会话,不切换) - self._send_key_press(VK_RETURN, 0.4) - # Ctrl+End 滚动到底部 - ctypes.windll.user32.keybd_event(VK_CONTROL, 0, 0, 0) - self._send_key_press(VK_END, 0.05) - ctypes.windll.user32.keybd_event(VK_CONTROL, 0, 2, 0) - time.sleep(0.3) - - # 用 SendInput 替代 auto.SendKeys(避免 ord() Unicode 解析 bug) + # 不发送 Esc/Esc/Enter 等键盘导航——TIM 会关闭窗口。 + # 改用 PostMessage 发送 Ctrl+A/C 到指定 hwnd,不泄露到其他窗口。 + # UIA 读取失败时回退到这条路径(PostMessage 对 Electron 可能无效, + # 但安全——不会误把 Ctrl+C 发给控制台)。 VK_A = 0x41 VK_C = 0x43 - # Ctrl+A (全选) - self._send_ctrl_combo(VK_A) + # Ctrl+A (全选) — PostMessage 到目标窗口,不泄露到控制台 + self._post_ctrl_combo(hwnd, VK_A) time.sleep(0.15) # Ctrl+C (复制) - self._send_ctrl_combo(VK_C) + self._post_ctrl_combo(hwnd, VK_C) time.sleep(0.3) wc.OpenClipboard() @@ -559,6 +542,23 @@ class MessageMonitor: ctypes.windll.user32.keybd_event(vk, 0, 2, 0) ctypes.windll.user32.keybd_event(VK_CONTROL, 0, 2, 0) + @staticmethod + def _post_key(hwnd, vk, delay=0.05): + """PostMessage 键盘到指定窗口(不全局,安全)。""" + win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, vk, 0) + time.sleep(delay) + win32gui.PostMessage(hwnd, win32con.WM_KEYUP, vk, 0) + + @staticmethod + def _post_ctrl_combo(hwnd, vk, delay=0.05): + """PostMessage Ctrl+组合键到指定窗口(不泄露到控制台)。""" + VK_CONTROL = 0x11 + win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, VK_CONTROL, 0) + win32gui.PostMessage(hwnd, win32con.WM_KEYDOWN, vk, 0) + time.sleep(delay) + win32gui.PostMessage(hwnd, win32con.WM_KEYUP, vk, 0) + win32gui.PostMessage(hwnd, win32con.WM_KEYUP, VK_CONTROL, 0) + @staticmethod def _is_system_message(text: str) -> bool: """过滤机器人/系统消息,不作为用户消息处理。""" @@ -691,16 +691,46 @@ class MessageMonitor: def _read_messages(self, window, platform: str = "") -> list: """每轮重新定位 ListControl。""" if platform == "qq": - # QQ NT UIA ListControl 缓存易过期,跳过 UIA 直接走剪贴板 - # 使用 _once 避免 _read_via_clipboard 的双重重试(减少前台抢占) + # TIM/QQ NT: 优先 UIA 深度扫描(无需键盘操作,不切焦点) + texts = self._read_message_list(window) + if texts: + return texts + texts = self._read_conversation_list(window) + if texts: + return texts + try: + rect = window.BoundingRectangle + boundary = rect.left + rect.width() * 0.40 + for lst in self._list_controls(window): + try: + cr = lst.BoundingRectangle + if cr.left >= boundary: + texts = self._extract_last_line(lst) + if texts: + logger.debug(f"[S-C] ListControl(x={cr.left}) → {len(texts)} items") + return texts + except Exception: + pass + except Exception: + pass + try: + texts = self._collect_filtered_texts(window, "qq", max_depth=5) + if texts: + logger.debug(f"[S-D] 全窗文本 → {len(texts)} items") + return texts + except Exception: + pass + # 剪贴板兜底:PostMessage 键盘到目标窗口(不全局 keybd_event,不会误杀进程) texts = self._read_via_clipboard_once(platform) if texts: return texts - + texts = self._read_via_clipboard(platform) + if texts: + return texts + # 最终兜底:win32 文本 texts = self._read_win32_texts(platform) if texts: return texts - return [] # 微信: 优先读"消息"(ScrollPattern 滚底后实时)