Skip to content

fix(recording): capture the window itself on Windows, not a crop of the display - #2301

Open
Stephen-Psaradellis wants to merge 1 commit into
CapSoftware:mainfrom
Stephen-Psaradellis:fix/windows-window-capture-item
Open

Stephen-Psaradellis wants to merge 1 commit into
CapSoftware:mainfrom
Stephen-Psaradellis:fix/windows-window-capture-item

Conversation

@Stephen-Psaradellis

@Stephen-Psaradellis Stephen-Psaradellis commented Sep 16, 2026

Copy link
Copy Markdown

Fixes #2151 in CapSoftware/Cap.

The bug. On Windows, a window recording never captures the window. create_d3d_capturer builds its GraphicsCaptureItem from 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 in scap-targets and had no caller.

The change.

  1. ScreenCaptureTarget::Window now 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.
  2. Using a window item makes the capture item resizable for the first time, which exposed a second bug in scap-direct3d: the frame pool is created at the item's size and never recreated, so after a resize each frame reports the new ContentSize while 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:

capture red pixels (the window on top) blue pixels (the window itself)
display item cropped to the window (before) 60000 0
the window's own capture item (after) 0 439796

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: 0 at 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 way crates/scap-targets/tests/windows_targets.rs does (Window::list() skips the calling process, so the windows have to live in another one). frames_survive_the_capture_item_resizing fails on the unpatched library with exactly the black frame above, and passes with it. Two unit tests in windows.rs pin 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-features clean, cargo fmt --all --check clean.

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 $100 on 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.

…he display

Window recording on Windows built its GraphicsCaptureItem from the display
and cropped it to the window's bounds, so anything stacked over the window
was recorded with it and switching virtual desktops recorded whatever took
its place. WindowImpl::try_as_capture_item already existed and was unused;
a window target now uses it, and drops the crop that carved the window out
of the display.

That makes the capture item resizable for the first time, which exposed a
second bug: the frame pool is created at the item's size and never
recreated, so after a resize the frames carry the new ContentSize while 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, and the
crop path skips frames whose crop box no longer fits the source.

Measured on Windows 11 with a blue window under a red always-on-top window:
the display-cropped capture contains 60000 red pixels, the window's own
capture item contains none. After resizing 786x593 -> 1086x693 the frames
were entirely black before this change and carry the window's contents
after it. Both are pinned by tests in crates/scap-direct3d/tests.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Window Recording Bug On Windows

1 participant