@
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:
|
try:
|
||||||
if hwnd:
|
if hwnd:
|
||||||
if win32gui.IsWindow(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:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -702,7 +710,7 @@ class MessageMonitor:
|
|||||||
w = auto.WindowControl(Name=name)
|
w = auto.WindowControl(Name=name)
|
||||||
if w.Exists(maxSearchSeconds=0.5):
|
if w.Exists(maxSearchSeconds=0.5):
|
||||||
rect = w.BoundingRectangle
|
rect = w.BoundingRectangle
|
||||||
if rect.width() > 200 and rect.height() > 100:
|
if rect.width() > 300 and rect.height() > 200:
|
||||||
h = w.NativeWindowHandle
|
h = w.NativeWindowHandle
|
||||||
# 确保存顶层窗口句柄(与 GetForegroundWindow 返回值一致)
|
# 确保存顶层窗口句柄(与 GetForegroundWindow 返回值一致)
|
||||||
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
||||||
@@ -745,7 +753,7 @@ class MessageMonitor:
|
|||||||
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
|
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
|
||||||
continue
|
continue
|
||||||
rect = win32gui.GetWindowRect(top)
|
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_hwnd[platform] = top
|
||||||
self._cached_pid[platform] = pid
|
self._cached_pid[platform] = pid
|
||||||
self._window_not_found_until.pop(platform, None)
|
self._window_not_found_until.pop(platform, None)
|
||||||
@@ -781,7 +789,7 @@ class MessageMonitor:
|
|||||||
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
|
if not (win32gui.IsWindow(top) and win32gui.IsWindowVisible(top)):
|
||||||
continue
|
continue
|
||||||
rect = win32gui.GetWindowRect(top)
|
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"
|
variant = "tim" if "TIM" in proc_name.upper() else "qq_nt"
|
||||||
self._app_variant[platform] = variant
|
self._app_variant[platform] = variant
|
||||||
self._cached_hwnd[platform] = top
|
self._cached_hwnd[platform] = top
|
||||||
@@ -799,15 +807,31 @@ class MessageMonitor:
|
|||||||
try:
|
try:
|
||||||
if child.ClassName == "TXGuiFoundation":
|
if child.ClassName == "TXGuiFoundation":
|
||||||
rect = child.BoundingRectangle
|
rect = child.BoundingRectangle
|
||||||
if rect.width() > 200 and rect.height() > 100:
|
if rect.width() > 300 and rect.height() > 200:
|
||||||
h = child.NativeWindowHandle
|
h = child.NativeWindowHandle
|
||||||
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
top = win32gui.GetAncestor(h, win32con.GA_ROOT)
|
||||||
pid = win32process.GetWindowThreadProcessId(top)[1]
|
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._app_variant[platform] = "qq_nt"
|
||||||
self._cached_hwnd[platform] = top
|
self._cached_hwnd[platform] = top
|
||||||
self._cached_pid[platform] = pid
|
self._cached_pid[platform] = pid
|
||||||
self._window_not_found_until.pop(platform, None)
|
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
|
return top
|
||||||
except Exception:
|
except Exception:
|
||||||
continue
|
continue
|
||||||
@@ -873,7 +897,7 @@ class MessageMonitor:
|
|||||||
w = auto.WindowControl(Name=name)
|
w = auto.WindowControl(Name=name)
|
||||||
if w.Exists(maxSearchSeconds=0.2):
|
if w.Exists(maxSearchSeconds=0.2):
|
||||||
rect = w.BoundingRectangle
|
rect = w.BoundingRectangle
|
||||||
if rect.width() > 200 and rect.height() > 100:
|
if rect.width() > 300 and rect.height() > 200:
|
||||||
return w
|
return w
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -883,7 +907,25 @@ class MessageMonitor:
|
|||||||
try:
|
try:
|
||||||
if child.ClassName == "TXGuiFoundation":
|
if child.ClassName == "TXGuiFoundation":
|
||||||
rect = child.BoundingRectangle
|
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
|
return child
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -1226,6 +1268,31 @@ class MessageMonitor:
|
|||||||
pass
|
pass
|
||||||
return False
|
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
|
@staticmethod
|
||||||
def _fuzzy_duplicate(new_text: str, seen_set: set, threshold: float = 0.80) -> bool:
|
def _fuzzy_duplicate(new_text: str, seen_set: set, threshold: float = 0.80) -> bool:
|
||||||
"""检查 new_text 是否与 seen_set 中已有文本相似度超阈值。
|
"""检查 new_text 是否与 seen_set 中已有文本相似度超阈值。
|
||||||
@@ -1945,7 +2012,7 @@ class MessageMonitor:
|
|||||||
hwnd = self._cached_hwnd.get("wechat")
|
hwnd = self._cached_hwnd.get("wechat")
|
||||||
if not hwnd:
|
if not hwnd:
|
||||||
return []
|
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_hwnd.pop("wechat", None)
|
||||||
self._cached_pid.pop("wechat", None)
|
self._cached_pid.pop("wechat", None)
|
||||||
return []
|
return []
|
||||||
|
|||||||
Reference in New Issue
Block a user