Repository navigation
fix(recording): capture the window itself on Windows, not a crop of the display - #2301
Stephen-Psaradellis wants to merge 4 commits into
Conversation
|
Thanks for the detailed write-up. Greptile hasn't posted a score yet, so please trigger a review (comment @greptileai) and address anything it raises. We need Greptile 5/5 and green CI before we review, and we'll want to verify window resizing mid-recording on Windows. |
| let capture_item = match params.window_id { | ||
| Some(window_id) => Window::from_id(window_id) | ||
| .ok_or_else(|| anyhow!("Window not found for ID: {:?}", window_id))? |
There was a problem hiding this comment.
If the user minimizes the selected window before capture setup reaches this code, Window::from_id cannot find it because that lookup uses Window::list(), whose enumeration excludes minimized windows. Capturer creation then returns “Window not found,” so the recording fails to start even though the selected window still exists. Resolve the stored HWND directly instead of requiring the target to remain eligible for enumeration.
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/recording/src/sources/screen_capture/windows.rs
Line: 535-537
Comment:
**Minimized Window Fails Setup**
If the user minimizes the selected window before capture setup reaches this code, `Window::from_id` cannot find it because that lookup uses `Window::list()`, whose enumeration excludes minimized windows. Capturer creation then returns “Window not found,” so the recording fails to start even though the selected window still exists. Resolve the stored HWND directly instead of requiring the target to remain eligible for enumeration.
---
For each issue above, determine whether it is valid and should be fixed. If so, fix it directly.…get still records Window::from_id searches Window::list(), and the Windows enumeration filter skips minimized windows (is_window_valid_for_enumeration rejects IsIconic). A window minimized between selection and capture setup was therefore reported as "Window not found" and the recording failed to start, even though the window still existed and was capturable. Add WindowImpl::from_id_direct, which rebuilds the HWND from the id (on Windows a WindowId is the HWND) and validates it with IsWindow, and use it at the capture-item call site. Enumeration eligibility is the wrong question once the user has already picked the target; liveness is the right one. Reported by Greptile on CapSoftware#2301.
|
Thanks — Greptile has now run and the Greptile Review check is green. It scored 4/5 and raised one P1, which was a real bug, so thank you for pushing for the review rather than waving it through. The P1, confirmed. Fixed in 232e1b5. On Windows a Window resizing mid-recording. This is in the PR description, but to put it where you are looking: a resize from 786x593 to 1086x693 while recording, sampling every frame. Before the frame-pool change every frame after the resize came back empty at the new size ( On CI: the only red check is Vercel, reporting "Authorization required to deploy" — that one needs authorization on your side, I can't clear it from here. One caveat I would rather state than have you discover: I verified Disclosure: I use an AI system to do this work; I review and am responsible for everything submitted under my name. |
|
@Stephen-Psaradellis thanks for digging into this, the before/after pixel test is really convincing. One blocker before we can take it: with the crop gone, the output size is still derived from the even-floored crop bounds (crates/recording/src/sources/screen_capture/mod.rs:499), but WGC now hands back frames at the window item's own size. Whenever those differ (odd window width/height, item size vs window rect, or max_capture_size constraining the output) every frame drops into the CPU readback + sws_scale fallback at windows.rs:593 for the entire recording, which is a big CPU and quality hit. Could you size the output from the window capture item (even-rounded) or crop the window frame to the even output size on the GPU, and add a test that a window recording never takes the scaler path? Two more things we need before merge: the Studio cursor overlay is still normalised against the window's start bounds, so it ends up in the wrong place once the window moves, and the frame pool Recreate in scap-direct3d now also changes what Display recordings do on a resolution change, so please rebase on main and include a real Windows run covering window move, resize mid-recording and a display resolution change for both Studio and Instant. |
…capture item Window targets capture the window's own GraphicsCaptureItem instead of a crop of the display. The output is sized from that item floored to even, and odd items are trimmed on the GPU (scap-direct3d Settings::even_dimensions), so a window recording's frames match the encoder size and never take the CPU scaler. The Studio cursor is placed against the window's current bounds, so it follows a moved window. Frame pool is recreated when the item resizes.
232e1b5 to
7971485
Compare
|
@Stephen-Psaradellis is attempting to deploy a commit to the Cap Software Inc Team on Vercel. A member of the Team first needs to authorize it. |
|
@richiemcilroy thanks, all three were real. Rebased on 1. Output size / scaler path. A window recording is now sized from the window's own capture item, floored to even ( Tests:
2. Studio cursor. 3. Real Windows run (Windows 11, 3440x1440 @ 165 Hz, the
Studio A Display recording on the same build ( Display resolution change: I did not run this one. Changing the display mode on this machine is not something I can do in this setup, so I have no recording to show for it and won't claim one. What changed for Displays is limited to the frame pool. Before, after a resolution change the pool kept vending surfaces at the old size, and the frame's |
|
@Stephen-Psaradellis thanks for the thorough follow-up, the capture item sizing with the GPU even trim and the cursor following the moved window both look right, and the Studio/Instant run with the move and odd resize is exactly what we needed. One thing left before we can merge: the frame pool |
Recreating the frame pool on a size change, and skipping frames a crop box no longer fits inside, now sit behind Settings::follow_item_size. windows.rs sets it alongside even_dimensions for window targets only, so display and area recordings behave exactly as on main.
|
Done in fd8daaa. Both behaviours now sit behind a new
I'll leave display resolution handling for a separate PR with its own on-device run, as you suggested. |
Fixes #2151 in
CapSoftware/Cap.The bug. On Windows, a window recording never captures the window.
create_d3d_capturerbuilds itsGraphicsCaptureItemfrom the display and crops it to the window's bounds, so the recording is a rectangle of the screen rather than the window: anything dragged over the window is recorded with it, and switching virtual desktops records whatever now occupies that rectangle.WindowImpl::try_as_capture_item(CreateForWindow) already existed inscap-targetsand had no caller.The change.
ScreenCaptureTarget::Windownow carries its window id through the capture config, and the Windows capturer builds the item from the window. The crop that carved the window out of the display is dropped for window targets — WGC already hands back just that window, and the crop additionally pinned the recording to wherever the window started out.scap-direct3d: the frame pool is created at the item's size and never recreated, so after a resize each frame reports the newContentSizewhile the pool's surfaces are still the old size, and every later frame reads back black. The pool is now recreated when the content size changes. The crop path also skips frames whose crop box no longer fits the source, instead of copying an out-of-range region and vending a stale surface.macOS is untouched: it builds its content filter from the display and crops, and
SCContentFilter's window path is a separate change.How it was tested. On Windows 11 (24H2, Rust 1.88.0), with a blue window under a red always-on-top window covering part of it:
and across a resize from 786x593 to 1086x693 while recording, sampling every frame: before the frame-pool change every frame after the resize was empty (
red: 0, blue: 0at the new size); after it the frames carry the window's contents (blue: 716496).Both are pinned by new tests in
crates/scap-direct3d/tests/window_capture.rs, which drive real windows from a fixture process the waycrates/scap-targets/tests/windows_targets.rsdoes (Window::list()skips the calling process, so the windows have to live in another one).frames_survive_the_capture_item_resizingfails on the unpatched library with exactly the black frame above, and passes with it. Two unit tests inwindows.rspin that a window target drops its crop and a display target keeps it.Also run:
cargo test -p scap-direct3d(7 passed),cargo test -p cap-recording --lib(552 passed, 0 failed, 4 ignored),cargo clippy -p scap-direct3d -p cap-recording --all-featuresclean,cargo fmt --all --checkclean.Disclosure. An AI system running an autonomous software project found the issue, wrote the fix and the tests, and wrote this description; I'm the account holder submitting it and I have reviewed the patch. Take it, change it or drop it as you see fit.
This fix is offered at a fixed $100, the floor I price fixes at (CapSoftware's completed Algora bounties run a $70 median and $550 largest). If you want to pay for it, comment
/bounty $100on the issue before merging and Algora pays it on merge. If you would rather not pay, merge it anyway - the code is yours either way.The PR appears safe to merge, with the previous minimized-window setup failure fully addressed and no new actionable defects identified.
Findings
Fix with agent prompt
Summary
This PR changes Windows recording to capture a selected window through its own
GraphicsCaptureItemrather than cropping the display. It also recreates the Direct3D frame pool when the capture item is resized, rejects invalid crop regions, and adds Windows integration tests for occlusion and resizing.Reviews (3) · Last reviewed commit: "fix(recording): resolve the window handl..." · Reviewed by Greptile