---
name: hd-self-screenshot-mac
description: >
  macOS platform companion to hd-self-screenshot — the Apple-specific half of HD
  screenshotting its own window, PLUS the token-efficient capture flow (`shot` — one
  call, prints a PNG path, never floods context with base64). The tool ladder, the
  `POST /screenshot` CDP endpoint, selector region-clips, the sharing gate, and the
  during-a-recording contention matrix live in hd-self-screenshot (adom/hd-bootstrap);
  THIS skill carries the `shot` helper doctrine and the macOS capture internals: the
  `screencapture` CLI / CoreGraphics window path, the CDP `Page.captureScreenshot`
  content grab, how the per-panel selector crop resolves the rect via `dom.measure`
  (WKWebView has no CDP) and crops with the image crate, and the Screen Recording TCC
  grant. Mirror of hd-self-screenshot-windows (Win32 PrintWindow/BitBlt). Trigger
  words — screenshot hd mac, shot helper, token efficient screenshot, screencapture
  cli, coregraphics screenshot, wkwebview no cdp, dom.measure crop,
  page.capturescreenshot mac, screen recording permission screenshot, tcc screenshot
  grant.
---

# hd-self-screenshot (macOS) — screencapture / CoreGraphics + the CDP content path

This is the **macOS half** of `hd-self-screenshot`. The default flow — `adom-cli
hydrogen screenshot panel|workspace|screen` saving a PNG into the container's
`screenshots/` to `Read`, the `POST /screenshot {target,selector,b64,silent}` CDP
power-path, the sharing gate, and the multi-thread `capture_busy` / coexistence matrix
— is cross-platform and lives in the generic **hd-self-screenshot** skill. Read that
first; this skill documents the token-efficient capture flow and what's Apple-specific
about how the bytes are grabbed.

## ⭐ ALWAYS capture with `shot` — 2 tool calls, ~20 bytes of output

**Never run `adom-cli hydrogen screenshot ...` bare.** It prints the capture as a
~300 KB base64 `dataUrl` JSON on stdout — that's ~85k tokens of noise in your context
per capture, plus a second decode step you'd have to improvise. This bootstrap installs
a wrapper that does the whole thing in one call and prints ONLY the PNG path:

```bash
shot                        # whole workspace (all panels)  → /tmp/shot-workspace-<ts>.png
shot screen                 # entire screen (desktop, other apps)
shot panel --name "KiCad"   # one panel's content
shot workspace --reason "verify the flow viewer rendered"   # flags pass through
```

Then **Read** the printed path — the Read tool attaches images efficiently. Total flow:
one Bash call + one Read. That's the floor; don't add steps.

- If no image comes back, `shot` exits 1 and prints the CLI's own JSON to stderr —
  usually the **sharing gate** (capture needs the user's sharing approval; pass
  `--reason "…"` so the approval dialog says why). Read the error, don't blind-retry.
- `SHOT_OUT=/path/out.png shot ...` overrides the output path if you need a stable name.
- If `shot` is missing (pre-0.1.3 workspace), re-converge: `adom-wiki pkg install
  adom/hd-mac-bootstrap` — or inline the same pipe:
  `adom-cli hydrogen screenshot workspace 2>/dev/null | python3 -c "import sys,json,base64;d=json.load(sys.stdin);open('/tmp/ws.png','wb').write(base64.b64decode(d['dataUrl'].split(',',1)[1]));print('/tmp/ws.png')"`

## How HD grabs its own window on macOS

- **Whole-window / desktop:** HD uses the macOS **`screencapture` CLI** (and
  CoreGraphics window capture) to grab the HD window or the full screen — the macOS
  analog of the Windows `PrintWindow`/`BitBlt` path.
- **Webview / panel content:** the canonical content grab is **CDP
  `Page.captureScreenshot`** (the `/capture/viewport` · `/shot` path) — a one-shot,
  wake-lock-free capture of the rendered surface. This is the same `POST /screenshot`
  endpoint as everywhere; on macOS it backs the `target`/panel content capture.

## ⭐ The per-panel selector crop — `dom.measure`, not CDP

On Windows the `selector` region-clip can lean on CDP against the webview. On macOS the
HD shell uses **WKWebView, which exposes no CDP** to drive the page directly. So a
per-panel / per-selector crop resolves the rect a different way:

1. Resolve the element's rectangle via the **`dom.measure` ui-action RPC** (the
   UI-command bus measures the selector in the live DOM and returns its bounds).
2. Capture the content area (CDP `Page.captureScreenshot` / `screencapture`).
3. **Crop the captured image to that rect with the Rust `image` crate.**

So `{selector:".resource-bars"}` still clips to just that element — but the rect comes
from `dom.measure` and the crop is server-side in Rust, because WKWebView can't be
CDP-shot to a selector the way a Chromium webview can.

## macOS gotcha — the Screen Recording TCC grant

The OS-level capture (`screencapture` / CoreGraphics) is gated by macOS **TCC: System
Settings → Privacy & Security → Screen Recording** must allow Hydrogen Desktop. Without
that grant a window/screen capture comes back **black or empty** even though the call
"succeeds." If screenshots are blank, check the Screen Recording grant first — it's the
same grant the desktop recording paths need ([hd-recording-mac]).

## NOT here (lives in the Windows companion)

The Win32 capture primitives — **`PrintWindow`** (per-window, works for backgrounded
windows) and **`BitBlt`** — are the Windows self-screenshot mechanics; see
`hd-self-screenshot-windows`. On macOS it's `screencapture`/CoreGraphics + CDP
`Page.captureScreenshot`, with `dom.measure`-driven cropping for selectors.
