diff --git a/electron/ipc/recording/windows.test.ts b/electron/ipc/recording/windows.test.ts index 5881eecc..0d561339 100644 --- a/electron/ipc/recording/windows.test.ts +++ b/electron/ipc/recording/windows.test.ts @@ -122,4 +122,9 @@ describe("native Windows window capture", () => { ); expect(windowsCaptureSource).not.toContain("nextWidth != captureWidth_"); }); + + it("does not expose desktop pixels when the selected window shrinks", () => { + expect(windowsCaptureSource).toContain("(std::min)(mappedWidth"); + expect(windowsCaptureSource).toContain("ClearRenderTargetView"); + }); }); diff --git a/electron/native/bin/win32-x64/helpers-manifest.json b/electron/native/bin/win32-x64/helpers-manifest.json index 5fe298ae..0172a0c3 100644 --- a/electron/native/bin/win32-x64/helpers-manifest.json +++ b/electron/native/bin/win32-x64/helpers-manifest.json @@ -5,10 +5,10 @@ "helpers": { "wgc-capture": { "binaryName": "wgc-capture.exe", - "binarySha256": "c0dca95216a46474f1c71e2274228cbc78dd09c49e1539333a76183b115b1c5a", + "binarySha256": "a5a9c0c417144a834af3206b60bae32d0c21d55deb8a3f5446831621cfdfa7b3", "sourceDir": "electron/native/wgc-capture", - "sourceFingerprint": "9d8ba313407468ddff200317b210e74953e683c9ccd2e27104dd9e94b8efa7cb", - "updatedAt": "2026-09-04T21:09:08.014Z" + "sourceFingerprint": "c65b6eb2230be7db9b49aac48eba52a9eac469028ebb242e10c16c51c86b3220", + "updatedAt": "2026-09-05T02:09:18.163Z" }, "cursor-monitor": { "binaryName": "cursor-monitor.exe", diff --git a/electron/native/bin/win32-x64/wgc-capture.exe b/electron/native/bin/win32-x64/wgc-capture.exe index 5ea3098e..efa45c37 100644 Binary files a/electron/native/bin/win32-x64/wgc-capture.exe and b/electron/native/bin/win32-x64/wgc-capture.exe differ diff --git a/electron/native/wgc-capture/src/wgc_session.cpp b/electron/native/wgc-capture/src/wgc_session.cpp index 95dd17da..1dde8081 100644 --- a/electron/native/wgc-capture/src/wgc_session.cpp +++ b/electron/native/wgc-capture/src/wgc_session.cpp @@ -256,20 +256,27 @@ bool WgcSession::updateWindowCropRect(bool initializeSize) { desc.Format = DXGI_FORMAT_B8G8R8A8_UNORM; desc.SampleDesc.Count = 1; desc.Usage = D3D11_USAGE_DEFAULT; + desc.BindFlags = D3D11_BIND_RENDER_TARGET; ComPtr resizedTexture; if (FAILED(d3dDevice_->CreateTexture2D(&desc, nullptr, &resizedTexture))) return false; + ComPtr renderTargetView; + if (FAILED(d3dDevice_->CreateRenderTargetView(resizedTexture.Get(), nullptr, &renderTargetView))) return false; cropTexture_ = resizedTexture; + cropRenderTargetView_ = renderTargetView; } - if (!cropTexture_) return false; + if (!cropTexture_ || !cropRenderTargetView_) return false; // The encoder's dimensions are fixed for the lifetime of the MP4. Keep the // crop texture fixed too: reallocating it after a resize made the encoder - // pad the changed frame with black bars. - left = std::clamp(left, 0L, static_cast(framePoolWidth_ - captureWidth_)); - top = std::clamp(top, 0L, static_cast(framePoolHeight_ - captureHeight_)); - cropRect_ = {left, top, left + captureWidth_, top + captureHeight_}; + // pad the changed frame with black bars. If the selected window shrinks, + // copy only its current area so the crop never exposes nearby desktop pixels. + const LONG copyWidth = (std::min)(mappedWidth, static_cast(captureWidth_)); + const LONG copyHeight = (std::min)(mappedHeight, static_cast(captureHeight_)); + left = std::clamp(left, 0L, static_cast(framePoolWidth_) - copyWidth); + top = std::clamp(top, 0L, static_cast(framePoolHeight_) - copyHeight); + cropRect_ = {left, top, left + copyWidth, top + copyHeight}; return true; } @@ -308,6 +315,7 @@ void WgcSession::stopCapture() { framePool_ = nullptr; } cropTexture_.Reset(); + cropRenderTargetView_.Reset(); } void WgcSession::onFrameArrived( @@ -349,6 +357,8 @@ void WgcSession::onFrameArrived( if (SUCCEEDED(hr) && texture && frameCallback_) { if (windowHandle_ && cropTexture_ && updateWindowCropRect()) { + constexpr float clearColor[4] = {0.0f, 0.0f, 0.0f, 1.0f}; + d3dContext_->ClearRenderTargetView(cropRenderTargetView_.Get(), clearColor); D3D11_BOX sourceBox{ static_cast(cropRect_.left), static_cast(cropRect_.top), 0, static_cast(cropRect_.right), static_cast(cropRect_.bottom), 1, diff --git a/electron/native/wgc-capture/src/wgc_session.h b/electron/native/wgc-capture/src/wgc_session.h index 3bc0968e..a2a15466 100644 --- a/electron/native/wgc-capture/src/wgc_session.h +++ b/electron/native/wgc-capture/src/wgc_session.h @@ -39,6 +39,7 @@ private: ComPtr d3dDevice_; ComPtr d3dContext_; ComPtr cropTexture_; + ComPtr cropRenderTargetView_; winrt::Windows::Graphics::DirectX::Direct3D11::IDirect3DDevice winrtDevice_{nullptr}; winrt::Windows::Graphics::Capture::GraphicsCaptureItem captureItem_{nullptr}; winrt::Windows::Graphics::Capture::Direct3D11CaptureFramePool framePool_{nullptr};