"""Authentic screenshot capture with a manifest (no synthetic imagery). Modes: native_live (a real Terminal/Safari window via /usr/sbin/screencapture -l ), dashboard_live (Safari showing the evidence viewer while runs execute), viewer_postrun (same viewer after runs), transcript_replay (not used unless labelled). Each capture records tool+version, surface, UTC time, SHA-256, linked run/event ids, selection rule and caption.""" from __future__ import annotations import hashlib, json, os, subprocess, datetime, re from .contracts import utcnow def _sha(p): return hashlib.sha256(open(p, "rb").read()).hexdigest() def window_id(app_name: str, title_substring: str = "") -> int | None: """Find a CGWindow id of a visible window of `app_name` (optionally with a title substring) via a tiny Swift-free route: osascript + python Quartz.""" try: import Quartz # type: ignore wins = Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements, Quartz.kCGNullWindowID) for w in wins: if w.get("kCGWindowOwnerName") == app_name and (title_substring in (w.get("kCGWindowName") or "")) and w.get("kCGWindowLayer", 0) == 0: return int(w["kCGWindowNumber"]) except Exception: # noqa: BLE001 pass # fallback: osascript (System Events) cannot give CGWindow ids reliably; return None return None def capture_window(wid: int, out_path: str) -> bool: os.makedirs(os.path.dirname(out_path), exist_ok=True) r = subprocess.run(["/usr/sbin/screencapture", "-x", "-o", "-l", str(wid), out_path], capture_output=True) return r.returncode == 0 and os.path.exists(out_path) and os.path.getsize(out_path) > 0 def capture_screen(out_path: str) -> bool: os.makedirs(os.path.dirname(out_path), exist_ok=True) r = subprocess.run(["/usr/sbin/screencapture", "-x", out_path], capture_output=True) return r.returncode == 0 and os.path.exists(out_path) def tool_version() -> str: try: v = subprocess.run(["sw_vers", "-productVersion"], capture_output=True, text=True).stdout.strip() return f"/usr/sbin/screencapture (macOS {v})" except Exception: # noqa: BLE001 return "/usr/sbin/screencapture" def record(store, screenshot_id, path, mode, surface, run_id=None, task_id=None, treatment=None, phase=None, event_id=None, selection_rule="", caption="", claim_ids="", redaction="none"): rec = {"screenshot_id": screenshot_id, "run_id": run_id, "task_id": task_id, "treatment": treatment, "phase": phase, "event_id": event_id, "capture_timestamp_utc": utcnow(), "capture_mode": mode, "capture_tool": tool_version(), "source_surface": surface, "original_path": path, "original_sha256": _sha(path), "redacted_public_path": None, "redacted_sha256": None, "redaction_description": redaction, "selection_rule": selection_rule, "caption": caption, "claim_ids": claim_ids} store.add_screenshot(rec) return rec def publish(store, screenshot_id, public_path, crop=None, description="copied without modification"): """Create the public copy (optionally cropped with sips) and record its hash.""" row = store.query("SELECT original_path FROM screenshots WHERE screenshot_id=?", (screenshot_id,))[0] os.makedirs(os.path.dirname(public_path), exist_ok=True) if crop: x, y, w, h = crop subprocess.run(["sips", "-c", str(h), str(w), "--cropOffset", str(y), str(x), row["original_path"], "--out", public_path], capture_output=True, check=True) else: subprocess.run(["cp", row["original_path"], public_path], check=True) with store.tx() as c: c.execute("UPDATE screenshots SET redacted_public_path=?, redacted_sha256=?, redaction_description=? WHERE screenshot_id=?", (public_path, _sha(public_path), description, screenshot_id)) return public_path def window_bounds(wid: int): import Quartz # type: ignore for w in Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionAll, Quartz.kCGNullWindowID): if int(w["kCGWindowNumber"]) == wid: b = w["kCGWindowBounds"]; return int(b["X"]), int(b["Y"]), int(b["Width"]), int(b["Height"]) return None def capture_region(bounds, out_path: str) -> bool: """Composited screen capture of a rectangle (used for Safari, whose web content is not captured by -l window capture).""" x, y, w, h = bounds; os.makedirs(os.path.dirname(out_path), exist_ok=True) r = subprocess.run(["/usr/sbin/screencapture", "-x", "-R", f"{x},{y},{w},{h}", out_path], capture_output=True) return r.returncode == 0 and os.path.exists(out_path) and os.path.getsize(out_path) > 0 def capture_safari_dashboard(store, screenshot_id, url, out_path, mode, **meta) -> dict | None: """Open/reload the URL in the front Safari window in the current Space, bring it to front, capture the composited region.""" import time subprocess.run(["osascript", "-e", 'tell application "Safari" to activate', "-e", f'tell application "Safari" to set URL of front document to "{url}"'], capture_output=True) time.sleep(4) import Quartz # type: ignore wins = Quartz.CGWindowListCopyWindowInfo(Quartz.kCGWindowListOptionOnScreenOnly | Quartz.kCGWindowListExcludeDesktopElements, Quartz.kCGNullWindowID) c = [w for w in wins if w.get("kCGWindowOwnerName") == "Safari" and w.get("kCGWindowLayer", 0) == 0 and w.get("kCGWindowName")] if not c: return None b = c[0]["kCGWindowBounds"]; bounds = (int(b["X"]), int(b["Y"]), int(b["Width"]), int(b["Height"])) if not capture_region(bounds, out_path): return None return record(store, screenshot_id, out_path, mode, f"Safari window '{c[0].get('kCGWindowName')}' → {url} (composited region capture of the evidence viewer over runs/state.sqlite)", **meta)