# TASK 38 — Capture Window… backdrop (in-app window picker submenu) **Status:** ✅ Done — shipped 2026-09-21 (Good Dog: ONE integration test) ## Idea (creator ruling 2026-09-21) Add an in-app **"Capture Window…"** submenu (PreviewPane backdrop context menu + the Live layer-row menu) listing every visible top-level window owned by another process. Picking one pins that window as the full-bleed Live backdrop at layer 0 — the same render path as desktop/game capture, captured via the pre-existing `window:` key in `ScreenCaptureSourceFactory.Resolve` (no new capture class, no compositor change). Rulings locked in the plan: 1. **In-app submenu, not the OS GraphicsCapturePicker** — the picker dialog is a different flow ("Change Capture…"); this is a catalog of open windows. 2. **Captured window is the Live backdrop at layer 0**; the app's own window never goes OS-fullscreen. 3. **Session-scoped, never persisted** — like `picker:` keys. **"A new session is a new session"**: a persisted `window:` key must NOT be resurrected on reload; an HWND can be recycled by the OS, so resurrecting it could capture an unrelated window (the exact hazard that makes persistence wrong). 4. **Pin wins while alive**: while the pinned window still enumerates, the auto fullscreen-game / desktop detector must not steal the backdrop. 5. **On window death → auto-fallback** through the existing chain (fullscreen game → desktop → static art). No dead ends. 6. **Manual "Refresh Window List"** menu item included (creator picked the manual refresh option). ## What shipped - `Services/Win32WindowEnumerator.cs` — real `EnumWindows` implementation of the `IWindowEnumerator` seam. Filters: visible, non-cloaked (`DWMWA_CLOAKED`), non-empty rect, other-process (never offers the app's own UI), non-empty title, non-empty process name; ordered by process name then title. Mirrors `Win32FullScreenDetector`'s window predicates so picker and capture-detector agree. - `ViewModels/MainViewModel.WindowBackdrop.cs` (new partial) — `OpenWindows` (`ObservableCollection`), `RefreshWindowsCommand`, `CaptureWindowCommand` (mirrors `RelayCommand` pattern; routes to `RedesignateBackgroundAsync(info.CaptureKey)`), `IsTransientCaptureKey` (`window:` + `picker:`), `IsAliveWindowPin`, and `OnScreenCaptureFailed` (the transient-key-session-failure → auto-reacquire heal). - `MainViewModel.Background.cs` — `_windowEnumerator` field + `WindowEnumeratorOverride` test seam (mirrors `CameraEnumeratorOverride`); `ReacquireScreenCaptures` now strips transient `window:`/`picker:` keys **before** auto-detection; `OnFullscreenMonitorChanged` skips redesignation while a live window pin is active ("pin wins while alive"). - `MainViewModel.cs` — ctor wires `_windowEnumerator`, `RefreshWindowsCommand`, `CaptureWindowCommand`, initial `RefreshOpenWindows()`, and routes `CaptureFailed` → `OnScreenCaptureFailed`. - UI: "Capture Window…" + "Refresh Window List" added to **both** the PreviewPane backdrop context menu (mirrors the "Capture Desktop" submenu binding pattern) and the Live layer-row menu (LeftPanel.xaml, `Tag`→`IsBackground` MultiBinding visibility like its siblings). - ONE integration test: `ytLive.Tests/WindowBackdropIntegrationTests.cs` (RealApp + temp DB + fake `IWindowEnumerator` via the static seam) — asserts (a) the seam feeds the submenu, (b) a persisted `window:0x…` key heals away on reload (never resurrected), (c) pinning an un-capturable window falls back to auto (no dead `window:` pin). ## Pre-existing corruption fixed (out-of-scope prerequisite) `ytLive.Tests/PillRadioTests.cs:105` carried a committed stray token (`Assert.Equal("Start", vm.PrimaryStartButtonLabel soil);`) that broke the entire test-project compile — the file was unwritable and NO tests could run. Removed the stray `soil` token (only change to the file). This is the fix for the "committed syntax error" landmine. ## Model / naming facts - `Source.CaptureKey` is persisted (`window:`, `monitor:`, `picker:`); `Source.IsBackground` marks the one Background per scene. See ai.md §Screen backdrop capture. - `WindowInfo` (Models) — `Title`, `ProcessName`, `Hwnd` (long), `ProcessId`; `Label => "{Title} — {ProcessName}"`; `CaptureKey => $"window:0x{Hwnd:X}"`. - `IWindowEnumerator` (Services) — `IReadOnlyList Enumerate()`; seam like `IFullScreenDetector`. - The catastrophic-accident guard lives ONLY at reload/reacquire. Mid-session, when the pinned window's capture session dies, `ScreenCaptureManager.CaptureFailed` → `OnScreenCaptureFailed` (transient key) → `ReacquireScreenCaptures()` → strips + auto-fallback. ## Acceptance - [x] Right-click live backdrop → "Capture Window…" lists open non-app windows with "Title — Process" labels. - [x] Picking one makes it the full-bleed Live backdrop. - [x] While the window lives, the fullscreen-game detector does not steal the backdrop. - [x] Window dies → auto-fallback (game → desktop → static), no dead-end `window:` pin. - [x] Closing/reopening the app never resurrects a prior session's `window:` key. - [x] "Refresh Window List" re-enumerates.