""" core/debug_log.py Tees stdout/stderr into an in-memory ring buffer + Qt signal so the Debug window can show everything the app has printed since startup — including messages from background poll threads (e.g. "[CMLLayer] poll error: ...") — not just whatever gets printed while the window happens to be open. install() should be called once, early, before anything prints. The real streams are still written to, so running from a terminal is unaffected. """ from __future__ import annotations import sys from collections import deque from typing import Optional from PyQt6.QtCore import QObject, pyqtSignal class _Broadcaster(QObject): line_written = pyqtSignal(str) class _StreamTee: def __init__(self, real_stream, lines: deque, broadcaster: _Broadcaster): self._real = real_stream self._lines = lines self._broadcaster = broadcaster def write(self, text: str) -> None: self._real.write(text) if text: self._lines.append(text) self._broadcaster.line_written.emit(text) def flush(self) -> None: self._real.flush() def isatty(self) -> bool: return False _lines: Optional[deque] = None _broadcaster: Optional[_Broadcaster] = None def install(max_lines: int = 2000) -> None: """Redirect sys.stdout/sys.stderr through the tee. Safe to call once.""" global _lines, _broadcaster if _broadcaster is not None: return _lines = deque(maxlen=max_lines) _broadcaster = _Broadcaster() sys.stdout = _StreamTee(sys.stdout, _lines, _broadcaster) sys.stderr = _StreamTee(sys.stderr, _lines, _broadcaster) def get_broadcaster() -> Optional[_Broadcaster]: return _broadcaster def get_history() -> str: return "".join(_lines) if _lines is not None else ""