Repository navigation
feat(recording): capture a window by itself, on request, on macOS and Windows - #3452
richiemcilroy wants to merge 21 commits into
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
|
hey @greptileai, please re-review the PR |
|
hey @greptileai, please re-review the PR |
| impl Drop for Frame { | ||
| fn drop(&mut self) { | ||
| if let Some(pool) = self.recycle.take() { | ||
| pool.give(self.texture.clone(), self.width, self.height); |
There was a problem hiding this comment.
Trimmed textures are recycled while native encoders still use them
Medium Severity
For an odd-sized window-only capture, native WindowsMuxer clones frame.texture() and drops ScreenFrame inside its input callback (output_pipeline/win.rs:353–358); WindowsSegmentedMuxer does the same at win_segmented.rs:550. This new Drop implementation immediately returns that texture to TrimPool, although H264Encoder::run only copies it in video_processor.process_texture after the callback returns. A concurrent capture can therefore take and overwrite the texture before that copy, corrupting the recorded frame; WindowsMuxer's last_texture also retains a recyclable texture for repeated frames. Keep a frame/lease alive through the encoder copy and frame-pacing retention, or recycle only once all texture users release ownership.
|
hey @greptileai, please re-review the PR |
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