""" modules/clipboard_control.py — Phase 4 (System Control): Clipboard utilities + history. No fake outputs: uses Win32 clipboard. History stored in data/clipboard_history.json. """ from __future__ import annotations import json import time from dataclasses import dataclass from pathlib import Path import win32clipboard # type: ignore import win32con # type: ignore DATA_DIR = Path(__file__).resolve().parents[1] / "data" HIST_PATH = DATA_DIR / "clipboard_history.json" @dataclass(frozen=True, slots=True) class ClipResult: ok: bool message: str def _read_text() -> str | None: try: win32clipboard.OpenClipboard() if win32clipboard.IsClipboardFormatAvailable(win32con.CF_UNICODETEXT): data = win32clipboard.GetClipboardData(win32con.CF_UNICODETEXT) return str(data) except Exception: return None finally: try: win32clipboard.CloseClipboard() except Exception: pass return None def snapshot() -> ClipResult: txt = _read_text() if not txt: return ClipResult(False, "Clipboard empty or non-text.") DATA_DIR.mkdir(parents=True, exist_ok=True) cur: list[dict] = [] if HIST_PATH.exists(): try: cur = json.loads(HIST_PATH.read_text(encoding="utf-8")) except Exception: cur = [] cur.append({"ts": time.time(), "text": txt[:2000]}) # Keep last 50 cur = cur[-50:] HIST_PATH.write_text(json.dumps(cur, ensure_ascii=False), encoding="utf-8") return ClipResult(True, "Clipboard saved.") def last(n: int = 5) -> ClipResult: if not HIST_PATH.exists(): return ClipResult(False, "No clipboard history yet.") try: cur = json.loads(HIST_PATH.read_text(encoding="utf-8")) cur = list(cur)[-max(1, int(n)) :] texts = [] for item in reversed(cur): t = str(item.get("text") or "").replace("\n", " ") texts.append(t[:120]) return ClipResult(True, " | ".join(texts)) except Exception: return ClipResult(False, "Clipboard history unreadable.")