feat(screenshot): Windows.Graphics.Capture path — transparent/GPU-composited windows capture correctly#48
Merged
Merged
Conversation
…posited windows capture correctly Windows capture now tries WGC (DXGI) first: D3D11 device (hardware, WARP fallback) -> GraphicsCaptureItem from the HWND via IGraphicsCaptureItemInterop -> free-threaded Direct3D11CaptureFramePool (we run on a tokio blocking thread, no DispatcherQueue) -> one frame within a bounded 2s wait -> CPU staging texture -> mapped BGRA rows (RowPitch-aware) -> straight RGBA, un-premultiplied and cropped to the client area so output dimensions match the legacy GDI path. WGC reads the DWM-composited surface, so WS_EX_LAYERED / transparent Tauri windows — which PrintWindow/BitBlt copy as uniform blank and which previously failed loudly — now return real pixels. GDI remains the fallback (older Windows builds without WGC, minimized windows where DWM stops composing, capture-session failures) and keeps its loud blank-frame error, now annotated with why the WGC attempt failed. Border/cursor suppression is best-effort (SetIsBorderRequired/SetIsCursorCaptureEnabled can fail on older builds and are ignored). Public entry points (capture_window / capture_window_raw) keep their signatures and client-area semantics for the filmstrip and trace consumers. Tests: 6 pure unit tests for the BGRA->RGBA conversion (RowPitch padding, client-area crop + clamping, undersized-buffer rejection, un-premultiply, zero-area/out-of-frame errors) plus an #[ignore]d live-desktop WGC test (foreground window; WGC rejects shell/desktop windows by design). Verified live on Windows 10: the ignored test passes, and a demo-app built from this branch served POST /api/tools/screenshot -> 900x600 PNG with real pixel variance via the WGC path for a window carrying WS_EX_LAYERED (exstyle 0xC0110). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CsePmfHDMBKRNDLNb3TLD5
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Windows screenshots now try Windows.Graphics.Capture (WGC) first, falling back to the existing GDI (
PrintWindow/BitBlt) path. WGC reads the DWM-composited surface, so transparent (WS_EX_LAYERED) and GPU-composited windows — which GDI copies as blank and which previously errored loudly — now capture with full content. This is the fix behind the 4DA notification-window blank-capture class, and the prerequisite for any future offscreen-webview capture.How
capture_window_raw(Windows) dispatches: WGC → blank-frame check → GDI fallback; the GDI loud blank-frame error is preserved and now annotated with the WGC failure reason, so a window neither engine can see still fails clearly instead of returning silent wrong pixels.CoInitializeEx(MTA) →IsSupportedgate → D3D11 device (hardware → WARP fallback for RDP/headless) →IGraphicsCaptureItemInterop::CreateForWindow→ free-threadedDirect3D11CaptureFramePool(no DispatcherQueue; we're on a tokio blocking thread) → bounded 2s first-frame wait (minimized windows time out instead of hanging) → CPU staging texture → RowPitch-aware BGRA→RGBA with un-premultiply (same math as the macOS path) and a client-area crop so output dimensions stay identical to the GDI path (PW_CLIENTONLY) forfilmstrip/trace/scrub consumers.SetIsBorderRequired(false)/SetIsCursorCaptureEnabled(false)— ignored on older builds (a yellow border is acceptable; a failed capture is not).windowsdep).Verification
#[ignore]d live test (wgc_captures_live_foreground_window).screenshotreturned a 900×600 PNG with 25 distinct colors / luma variance 1454 from a window carryingWS_EX_LAYERED(exstyle0xC0110), WGC path confirmed taken.--all-targets/--no-default-features/--releaseall-D warnings, fmt,cargo test -p victauri-plugin(878 passed). Independently re-run by a second reviewer session.Known limitations (documented in code)
trace.🤖 Generated with Claude Code
https://claude.ai/code/session_01CsePmfHDMBKRNDLNb3TLD5