@
fix: window activation bug - verify PID before using cached HWND Root cause: _ensure_hwnd cached HWND without verifying the owning process. When target app exits, OS reuses HWND value for another process, causing activation of wrong window (e.g. browser/editor disguised as QQ/WeChat). Changes: - Add _validate_wechat_window() to verify process name == WeChat.exe - _ensure_hwnd cache hit: verify HWND belongs to target process via PID - TXGuiFoundation fallback: verify PID belongs to QQ.exe/TIM.exe - _find_qq_window_fresh: same PID verification for TXGuiFoundation - _ocr_read_wechat: validate window before OCR capture - Raise min window size threshold from 200x100 to 300x200 everywhere @
This commit is contained in:
+76
-9
@@ -684,7 +684,15 @@ class MessageMonitor:
|
||||
try:
|
||||
if hwnd:
|
||||
if win32gui.IsWindow(hwnd):
|
||||
return hwnd
|
||||
# 验证窗口进程是否仍属于目标平台
|
||||
if platform == "wechat":
|
||||
if self._validate_wechat_window(hwnd):
|
||||
return hwnd
|
||||
elif platform == "qq":
|
||||
if self._validate_qq_window(hwnd):
|
||||
return hwnd
|
||||
else:
|
||||
return hwnd
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
@@ -702,7 +710,7 @@ class MessageMonitor:
|
||||
w = auto.WindowControl(Name=name)
|
||||
if w.Exists(maxSearchSeconds=0.5):
|
||||
rect = w.BoundingRectangle
|
||||
if rect.width() > 200 and rect.height() > 100:
|
||||
if rect.width() > 300 and rect.height() > 200:
|
||||
h = w.NativeWindowHandle
|
||||
# 确保存顶层窗口句柄(与 GetForegroundWindow 返回值一致)
|
||||
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
||||
@@ -745,7 +753,7 @@ class MessageMonitor:
|
||||
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
|
||||
continue
|
||||
rect = win32gui.GetWindowRect(top)
|
||||
if (rect[2] - rect[0]) > 200 and (rect[3] - rect[1]) > 100:
|
||||
if (rect[2] - rect[0]) > 300 and (rect[3] - rect[1]) > 200:
|
||||
self._cached_hwnd[platform] = top
|
||||
self._cached_pid[platform] = pid
|
||||
self._window_not_found_until.pop(platform, None)
|
||||
@@ -781,7 +789,7 @@ class MessageMonitor:
|
||||
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
|
||||
continue
|
||||
rect = win32gui.GetWindowRect(top)
|
||||
if (rect[2] - rect[0]) > 200 and (rect[3] - rect[1]) > 100:
|
||||
if (rect[2] - rect[0]) > 300 and (rect[3] - rect[1]) > 200:
|
||||
variant = "tim" if "TIM" in proc_name.upper() else "qq_nt"
|
||||
self._app_variant[platform] = variant
|
||||
self._cached_hwnd[platform] = top
|
||||
@@ -799,15 +807,31 @@ class MessageMonitor:
|
||||
try:
|
||||
if child.ClassName == "TXGuiFoundation":
|
||||
rect = child.BoundingRectangle
|
||||
if rect.width() > 200 and rect.height() > 100:
|
||||
if rect.width() > 300 and rect.height() > 200:
|
||||
h = child.NativeWindowHandle
|
||||
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
||||
pid = win32process.GetWindowThreadProcessId(top)[1]
|
||||
# 验证进程是否属于 QQ/TIM
|
||||
try:
|
||||
hproc = win32api.OpenProcess(
|
||||
0x0400 | 0x0010, # PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
|
||||
False, pid
|
||||
)
|
||||
if hproc:
|
||||
exe_path = win32process.GetModuleFileNameEx(hproc, 0)
|
||||
win32api.CloseHandle(hproc)
|
||||
exe_name = exe_path.split('\\')[-1].lower() if exe_path else ''
|
||||
if exe_name not in ('qq.exe', 'tim.exe'):
|
||||
continue # 不是QQ/TIM,跳过
|
||||
else:
|
||||
continue # 无法打开进程,跳过
|
||||
except Exception:
|
||||
continue # 验证失败,跳过
|
||||
self._app_variant[platform] = "qq_nt"
|
||||
self._cached_hwnd[platform] = top
|
||||
self._cached_pid[platform] = pid
|
||||
self._window_not_found_until.pop(platform, None)
|
||||
logger.info(f"[{platform}] 窗口已定位(UIA TXGuiFoundation): top={top}")
|
||||
logger.info(f"[{platform}] 窗口已定位(UIA TXGuiFoundation): top={top} pid={pid}")
|
||||
return top
|
||||
except Exception:
|
||||
continue
|
||||
@@ -873,7 +897,7 @@ class MessageMonitor:
|
||||
w = auto.WindowControl(Name=name)
|
||||
if w.Exists(maxSearchSeconds=0.2):
|
||||
rect = w.BoundingRectangle
|
||||
if rect.width() > 200 and rect.height() > 100:
|
||||
if rect.width() > 300 and rect.height() > 200:
|
||||
return w
|
||||
except Exception:
|
||||
pass
|
||||
@@ -883,7 +907,25 @@ class MessageMonitor:
|
||||
try:
|
||||
if child.ClassName == "TXGuiFoundation":
|
||||
rect = child.BoundingRectangle
|
||||
if rect.width() > 200 and rect.height() > 100:
|
||||
if rect.width() > 300 and rect.height() > 200:
|
||||
# 验证进程是否属于 QQ/TIM
|
||||
h = child.NativeWindowHandle
|
||||
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
||||
pid = win32process.GetWindowThreadProcessId(top)[1]
|
||||
try:
|
||||
hproc = win32api.OpenProcess(
|
||||
0x0400 | 0x0010, False, pid
|
||||
)
|
||||
if hproc:
|
||||
exe_path = win32process.GetModuleFileNameEx(hproc, 0)
|
||||
win32api.CloseHandle(hproc)
|
||||
exe_name = exe_path.split('\\')[-1].lower() if exe_path else ''
|
||||
if exe_name not in ('qq.exe', 'tim.exe'):
|
||||
continue
|
||||
else:
|
||||
continue
|
||||
except Exception:
|
||||
continue
|
||||
return child
|
||||
except Exception:
|
||||
pass
|
||||
@@ -1226,6 +1268,31 @@ class MessageMonitor:
|
||||
pass
|
||||
return False
|
||||
|
||||
def _validate_wechat_window(self, hwnd: int) -> bool:
|
||||
"""验证窗口是否确实是微信进程。"""
|
||||
if not win32gui.IsWindow(hwnd):
|
||||
return False
|
||||
if not win32gui.IsWindowVisible(hwnd):
|
||||
return False
|
||||
if win32gui.IsIconic(hwnd):
|
||||
return False
|
||||
# 验证进程名
|
||||
try:
|
||||
_, pid = win32process.GetWindowThreadProcessId(hwnd)
|
||||
hproc = win32api.OpenProcess(
|
||||
0x0400 | 0x0010, # PROCESS_QUERY_INFORMATION | PROCESS_VM_READ
|
||||
False, pid
|
||||
)
|
||||
if hproc:
|
||||
exe_path = win32process.GetModuleFileNameEx(hproc, 0)
|
||||
win32api.CloseHandle(hproc)
|
||||
exe_name = exe_path.split('\\')[-1].lower() if exe_path else ''
|
||||
if exe_name in ('wechat.exe',):
|
||||
return True
|
||||
except Exception:
|
||||
pass
|
||||
return False
|
||||
|
||||
@staticmethod
|
||||
def _fuzzy_duplicate(new_text: str, seen_set: set, threshold: float = 0.80) -> bool:
|
||||
"""检查 new_text 是否与 seen_set 中已有文本相似度超阈值。
|
||||
@@ -1945,7 +2012,7 @@ class MessageMonitor:
|
||||
hwnd = self._cached_hwnd.get("wechat")
|
||||
if not hwnd:
|
||||
return []
|
||||
if not win32gui.IsWindow(hwnd):
|
||||
if not win32gui.IsWindow(hwnd) or not self._validate_wechat_window(hwnd):
|
||||
self._cached_hwnd.pop("wechat", None)
|
||||
self._cached_pid.pop("wechat", None)
|
||||
return []
|
||||
|
||||
Reference in New Issue
Block a user