修TIM全局keybd_event误杀:删Esc键盘导航+PostMessage替代+恢复UIA多层读取
This commit is contained in:
+58
-28
@@ -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 滚底后实时)
|
||||
|
||||
Reference in New Issue
Block a user