diff --git a/monitor.py b/monitor.py index 9d2e035..454387c 100644 --- a/monitor.py +++ b/monitor.py @@ -1049,6 +1049,29 @@ class MessageMonitor: ) return (win_text, chosen) if chosen == "windows" else (easy_text, chosen) + @staticmethod + def _safe_screenshot(bbox: tuple, timeout: float = 3.0): + """带超时的屏幕截图。ImageGrab.grab() 的 BitBlt 可能因 DWM 卡死→整机冻结 2-3min。 + + 用线程+timeout 隔离:BitBlt 卡死只杀线程,不波及 DWM/桌面。 + timeout=3.0: 3 秒截不到就跳过本轮,bot 继续下次轮询。 + """ + _im = [None] + def _grab(): + try: + _im[0] = ImageGrab.grab(bbox=bbox) + except Exception: + pass + + import threading + t = threading.Thread(target=_grab, daemon=True) + t.start() + t.join(timeout=timeout) + if t.is_alive(): + logger.warning(f"[ocr] 截图超时 ({timeout}s),BitBlt 可能卡在 DWM,跳过本轮") + return None + return _im[0] + def _ocr_dual_engine(self, im_processed: Image.Image, im_original: Image.Image, platform: str) -> tuple: """双引擎并发 OCR:Windows OCR + EasyOCR 同时跑,最多等 10 秒。 @@ -1168,8 +1191,9 @@ class MessageMonitor: win_h = bottom - top # QQ NT 窗口布局: ~0-5% 标签栏, ~5-30% 联系人/群列表, ~30-55% 会话列表, ~55-100% 消息面板 # 先确保 QQ 可见再截屏,避免 CMD 窗口遮挡→OCR 读到终端文字 + # sleep 0.4s 让 DWM 完成窗口激活的合成,防止紧接的 BitBlt 卡死 self._ensure_visible(hwnd) - time.sleep(0.1) + time.sleep(0.4) variant = self._app_variant.get("qq", "") # TIM (Electron) 安全:不做 PostMessage 点击 + 不做 keybd_event 全局键 @@ -1212,7 +1236,9 @@ class MessageMonitor: cap_bottom = bottom - max(100, int(win_h * 0.10)) # 输入框上方 if cap_right <= cap_left or cap_bottom <= cap_top: return [] - im = ImageGrab.grab(bbox=(cap_left, cap_top, cap_right, cap_bottom)) + im = self._safe_screenshot((cap_left, cap_top, cap_right, cap_bottom)) + if im is None: + return [] if im.width < 50 or im.height < 50: return [] @@ -1308,10 +1334,13 @@ class MessageMonitor: return [] # 先确保微信可见再截屏,避免终端窗口遮挡 → OCR 读到终端文字 + # sleep 0.4s 让 DWM 完成窗口激活的合成,防止紧接的 BitBlt 卡死 self._ensure_visible(hwnd) - time.sleep(0.1) + time.sleep(0.4) - im = ImageGrab.grab(bbox=(cap_left, cap_top, cap_right, cap_bottom)) + im = self._safe_screenshot((cap_left, cap_top, cap_right, cap_bottom)) + if im is None: + return [] if im.width < 50 or im.height < 50: return []