feat(windows): per-window capture into a Flutter texture - #1
Merged
Conversation
Implements the remaining v1 slice for the Windows backend: live capture via Windows.Graphics.Capture, one-shot PNG grabs, and the engagement events the README already claimed were there. Capture never round-trips pixels through the CPU. Each frame is copied into one of two BGRA textures created with D3D11_RESOURCE_MISC_SHARED and the engine opens that shared handle directly; the pair rotates so the engine is never reading the surface being overwritten. The frame pool resizes itself when a source reflows, which is what keeps a live stream sharp after the user drags a window edge. One-shot grabs run on a worker with a 2s bound and encode through WIC, hopping back to the platform thread via a message-only-window task runner — a MethodResult cannot be completed off that thread. Engagement events close an honesty gap: the backend previously emitted only engaged and disengaged-on-release, so an externally moved or closed window silently desynced. SetWinEventHook now pushes the same three events the macOS backend does — alignmentBroken on external move/resize, disengaged(windowClosed) on destroy, and disengaged(thirdApp | hostActivated) on focus change — with engage and conform masking their own moves for 1.5s, mirroring macOS's selfMutationDeadline. Minimize stays a poll, as it is on macOS. detectSeparateWindows is implemented rather than stubbed to empty. Verified against real windows by rendering a live Texture and then re-capturing the harness's own window: the result shows one window composited inside another, which is what proves the shared-handle path end to end rather than inferring it from a frame counter. That test also caught IsCursorCaptureEnabled and IsBorderRequired being properties rather than methods, so the ApiInformation::IsMethodPresent feature check was silently leaving cursor capture on. Builds clean in Debug and Release under /W4 /WX. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
C++/WinRT routes its coroutine support through <experimental/coroutine> when built as C++17. MSVC 14.51 hard-errors on that header (STL1011) ahead of removing it, which broke CI on the VS 18 runner while building fine on a local 14.44 toolchain. Raising just this target to C++20 makes C++/WinRT select the standard <coroutine> header instead, rather than silencing the deprecation with _SILENCE_EXPERIMENTAL_COROUTINE_DEPRECATION_WARNINGS — the experimental header is going away, so suppressing it only moves the break later. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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.
Implements the remaining v1 slice for the Windows backend, so both platforms now satisfy the full platform-interface contract.
Capture
Live capture via
Windows.Graphics.Capture, delivered to Flutter as DXGI shared handles — pixels never round-trip through the CPU, matching what the macOS backend achieves with CVPixelBuffers.Each frame is copied into one of two BGRA textures created with
D3D11_RESOURCE_MISC_SHARED; the engine opens that handle directly. The pair rotates so the engine is never reading the surface being overwritten. The frame pool resizes itself when a source reflows, which is what keeps a live stream sharp after the user drags a window edge.One-shot grabs (
captureSingleFrame,captureSingleDisplayFrame) run on a worker with a 2s bound and encode through WIC, hopping back to the platform thread via a message-only-window task runner — aMethodResultcannot be completed off that thread.Engagement events
This closes an honesty gap: the backend previously emitted only
engagedanddisengaged-on-release, so an externally moved or closed window silently desynced.SetWinEventHooknow pushes the same three events the macOS backend does:alignmentBrokenon external move/resizedisengaged(windowClosed)on destroydisengaged(thirdApp | hostActivated)on focus changeEngage and conform mask their own moves for 1.5s, mirroring macOS's
selfMutationDeadline. Minimize stays a poll, as it is on macOS.detectSeparateWindowsis implemented rather than stubbed to empty.Verification
Ran against real windows on Windows 11: 21 checks covering enumeration, both one-shot paths, stream lifecycle, geometry, and the event stream.
The decisive check rendered a live
Textureand then re-captured the harness's own window — the result shows one window composited inside another, which proves the shared-handle path end to end rather than inferring it from a frame counter. That test also caughtIsCursorCaptureEnabledandIsBorderRequiredbeing properties rather than methods, so theApiInformation::IsMethodPresentfeature check was silently leaving cursor capture on.Builds clean in Debug and Release under
/W4 /WX; all four packages analyze with no issues.Notes for review
HMONITORvalues. Windows has noCGDirectDisplayIDequivalent, socaptureSingleDisplayFrametakes the monitor handle, and accepts'0'for the primary display.getContentMeasurementreports zero insets by design — DWM composites shadows outside the window, so there is no transparent margin to crop. A consumer treating zero as "not yet measured" rather than "nothing to trim" would behave differently here than on macOS.reconfigureStreamreports rather than applies, since the capture pool resizes itself.README.md,pubspec.yamldescription) are prose only; the macOS backend is untouched.🤖 Generated with Claude Code