Repository navigation
feat(recording): capture a window by itself, on request, on macOS and Windows - #3452
Conversation
Window targets on macOS were recorded as a display capture cropped to the window's bounds at start time, so moving the window or overlapping it with other apps changed what was recorded (#1754). Carry the window id into the macOS capture config and build a desktop-independent window SCContentFilter for it, skipping the display crop. The same filter is used when the capturer is rebuilt after a system stop. Display, Area and Camera Only targets are unchanged. Instant mode keeps the display crop when a camera is attached, because macOS Instant recordings have no camera compositing and the camera preview only appears through the display capture. Known limitations: - Studio custom cursor positions are still normalised against the window's start bounds, so the cursor overlay drifts after the window moves. - Output size is fixed at the window's size at start; resize behaviour is untested. - With system audio on, ScreenCaptureKit likely limits audio to the window's owning app. - If the window is missing from shareable content (initially or on restart), capture falls back to the previous display crop. Window-filter approach also explored in #2368.
The window-only SCContentFilter ignored the "Hide windows from recordings" exclusions, so selecting an excluded window in Window mode recorded it. When the selected window is in the exclusion list, skip the window filter and use the display filter with exclusions, which hides it as before. The check runs both when capture starts and when the capturer is rebuilt.
…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.
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.
…m test The test resizes the window twice; without follow_item_size the frame pool keeps its first surfaces while the trim copy uses the new ContentSize.
…tSize ContentSize is the item's size at capture time, and it runs ahead of the frame's surface whenever the item grows before the pool is recreated. That includes frames the old pool had already queued when Recreate ran: they carry the new ContentSize on a surface still at the old size, so the even-dimension trim copied a source box past the surface's edge. Hardware drivers ignore that; WARP removes the device, and Windows.Graphics.Capture then fails fast (0xc0000409) on its next use of it. - skip frames whose surface does not match their ContentSize when following the item's size (queued before the pool was recreated) - clamp the even-dimension trim and the crop check to the surface size - report a removed device instead of recreating the pool on it Tests: - can run on WARP (CAP_SCAP_TEST_WARP=1 for all, always for the two new ones) and fail if the capture device was removed - new: frames queued behind a slow consumer are not copied on recreate - new: the trim stays inside the surface without follow_item_size - size and place the fixture windows from the primary work area so the stacked-window crop fits on a 1024x768 display
…r than the frame The plain pass-through branch labelled the frame with ContentSize but handed over the whole pool surface. On Server 2022 a window item's surface includes the invisible resize borders, so as_buffer's CopyResource into a ContentSize staging texture was a copy between different sizes, which D3D11 drops, and the readback was all zeros. Every branch now bounds the frame by both ContentSize and the surface and copies that region when it differs from the surface. The even-dimension tests now expect the frame's ContentSize instead of item.Size(), and a new test shrinks the window under a fixed pool to reach the same path on any machine.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
hey @greptileai, please re-review the PR |
…the cursor follows the window on macOS, and trimmed D3D frames stop sharing a texture
| ); | ||
| display | ||
| .raw_handle() | ||
| .as_content_filter_excluding_windows(content.retained(), left_out) |
There was a problem hiding this comment.
macOS window-only capture no longer follows the window it records
High Severity
Recording a window with window-only capture on macOS and moving or resizing it mid-recording silently corrupts the output. The video crop is fixed once at setup: ScreenCaptureConfig::init (crates/recording/src/sources/screen_capture/mod.rs:516) trims the window's start-time bounds into config.crop_bounds, and the call anchored here stamps those bounds as sourceRect onto a display content filter that never tracks the window — this replaced ContentFilter::with_desktop_independent_window, which followed the window wherever it went. rebuild_capturer reuses the same stale params.config.crop_bounds, so not even a macOS-forced restart re-aims the crop. Meanwhile the cursor recorder recomputes macos_window_crop from the window's live bounds every tick (crates/recording/src/cursor.rs:795), so after the window moves the cursor is normalized against the new position while the video shows the original screen region: the cursor overlay is displaced by exactly the window's movement, and the frame shows whatever now occupies the start location. That contradicts the feature's own contract: RecordingBaseInputs.window_only and the with_window_only builders say record only that window, following it, on macOS and Windows, and instead of the region of the screen it starts in, and cursor.rs states A window recording follows the window. The same one-shot snapshot also fails the window-by-itself premise in a second way: the windows_in_front exclusion list is captured at filter-build time, so any window that arrives or drags over the target later is recorded on top of it. Fix by re-aiming sourceRect and refreshing the in-front exclusion list from the window's current bounds whenever it moves or resizes, so the video and cursor stay on the same trimmed frame. (Also: prior finding 01M5Z3YHMANHZWDB1KX01WD7P0 is resolved at this head — source_rect and its unconditional 8pt origin are deleted and both set_src_rect call sites pass crop_bounds position and size verbatim, matching the cursor trim for every window size.)
…t out of a window-only recording
…es, so window targets have bounds
…ndows and areas are not resampled
Stacked on #3450.
This brings in #2469 by @MinitJain (macOS) and #2301 by @Stephen-Psaradellis (Windows) unchanged, merged with their history. On top of them is one commit that makes window-only capture opt-in.
Problem
A window recording today is a crop of its display, taken where the window was when recording started. Anything dragged over the window is recorded, and if the window moves the recording keeps showing the old rectangle. #2469 and #2301 fix this by capturing the window itself, through a
ScreenCaptureKitdesktop-independent window filter and a windowGraphicsCaptureItem. Both do it for every window recording, which changes what a window recording means for everyone.Change
RecordingBaseInputs::window_onlyandActorBuilder::with_window_onlyon both the studio and instant actors. The default isfalse, so every existing caller records exactly as before.windowfield andinitparameter, gated on macOS and Windows respectively. They are now one#[cfg(any(target_os = "macos", windows))]field, filled throughwindow_only_target(target, window_only).CGWindowListCopyWindowInfocall for that window per tick (Window::from_id_direct), not a full window list. Linux keeps its existing X11 window source.WindowGoneinstead of falling back to the display crop. A crop would record whatever now covers the window's old rectangle.instant_capture_windownow applies the same rule to both platforms.One change from #2301 reaches the default path. On Windows, a cropped capture now skips a frame whose crop box no longer fits the source instead of copying out of range and vending a stale surface.
The app wiring (the Focus toggle and the window capture style setting) lands separately.
Test
cargo test -p cap-recording --libon macOS covers:instant_captures_the_window_only_when_asked_and_without_a_camera.scap-direct3d/tests/window_capture.rsand its cursor test, which run on Windows.cargo check -p cap-recording -p scap-targets --examples --testson macOS, andcargo xwin check -p scap-direct3d --testsfor Windows.

Confidence Score: 5/5
No new blocking finding is reported; the texture-lifetime concern belongs to an existing thread.Summary
Adds opt-in window-only recording on macOS and Windows while keeping display-crop recording as the default.
Reviews (5) · Last reviewed commit: "Merge feat/gpui-area-aspect-picker into ..." · Reviewed by Greptile