[feat] add Windows privacy screen and prefer DXGI capture

This commit is contained in:
dijunkun
2026-09-11 16:11:50 +08:00
parent 4f200095f4
commit ceb0a1654a
57 changed files with 4208 additions and 217 deletions
@@ -1,11 +1,12 @@
#include "screen_capturer_dxgi.h"
#include <display_stream_id.h>
#include <algorithm>
#include <chrono>
#include <string>
#include <vector>
#include <display_stream_id.h>
#include "libyuv.h"
#include "rd_log.h"
@@ -218,11 +219,13 @@ void ScreenCapturerDxgi::EnumerateDisplays() {
mi.rcMonitor.left, mi.rcMonitor.top,
mi.rcMonitor.right, mi.rcMonitor.bottom);
// primary first
if (is_primary)
if (is_primary) {
display_info_list_.insert(display_info_list_.begin(), info);
else
outputs_.insert(outputs_.begin(), output);
} else {
display_info_list_.push_back(info);
outputs_.push_back(output);
outputs_.push_back(output);
}
}
}
}
@@ -246,6 +249,11 @@ bool ScreenCapturerDxgi::CreateDuplicationForMonitor(int monitor_index) {
}
staging_.Reset();
DXGI_OUTDUPL_DESC desc{};
duplication_->GetDesc(&desc);
rotation_ = desc.Rotation;
LOG_INFO("DXGI: duplication ready, monitor={}, rotation={}", monitor_index,
static_cast<int>(rotation_));
return true;
}
@@ -276,6 +284,7 @@ bool ScreenCapturerDxgi::RecreateDuplicationForCurrentMonitor() {
}
void ScreenCapturerDxgi::ReleaseDuplication() {
++duplication_generation_;
staging_.Reset();
if (duplication_) {
duplication_->ReleaseFrame();
@@ -284,16 +293,27 @@ void ScreenCapturerDxgi::ReleaseDuplication() {
}
void ScreenCapturerDxgi::CaptureLoop() {
const int timeout_ms = 33;
const int timeout_ms = (std::max)(1, 1000 / (std::max)(1, fps_));
bool cached_frame_valid = false;
int cached_monitor = -1;
uint64_t cached_generation = 0;
std::vector<uint8_t> rotated_frame;
auto last_duplication_retry =
std::chrono::steady_clock::now() - std::chrono::milliseconds(1000);
while (running_) {
if (paused_) {
cached_frame_valid = false;
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
// Duplication/staging textures belong to one monitor. Do not release or
// replace them in SwitchTo while AcquireNextFrame/Map is using them.
std::unique_lock capture_lock(switch_mutex_);
if (paused_ || !running_) continue;
if (!duplication_) {
cached_frame_valid = false;
capture_lock.unlock();
const auto now = std::chrono::steady_clock::now();
if (now - last_duplication_retry >= std::chrono::milliseconds(500)) {
last_duplication_retry = now;
@@ -303,15 +323,32 @@ void ScreenCapturerDxgi::CaptureLoop() {
continue;
}
const int frame_monitor = monitor_index_.load();
DXGI_OUTDUPL_FRAME_INFO frame_info{};
Microsoft::WRL::ComPtr<IDXGIResource> desktop_resource;
HRESULT hr = duplication_->AcquireNextFrame(
timeout_ms, &frame_info, desktop_resource.GetAddressOf());
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
// DXGI explicitly reports an unchanged desktop. Re-emit only a frame
// successfully acquired for this monitor, so a static screen still has
// a delivery heartbeat after temporary privacy probes are removed.
if (cached_frame_valid && cached_generation == duplication_generation_ &&
cached_monitor == monitor_index_.load() && callback_ && nv12_frame_) {
const auto stream_id = MakeDisplayStreamId(cached_monitor);
capture_lock.unlock();
callback_(nv12_frame_, nv12_width_ * nv12_height_ * 3 / 2, nv12_width_,
nv12_height_, stream_id.c_str(), nullptr);
}
continue;
}
// Never replay a cached image after an acquisition/conversion failure or
// a backend rebuild. Normal error/reset handling must pause privacy.
cached_frame_valid = false;
if (FAILED(hr)) {
LOG_ERROR("DXGI: AcquireNextFrame failed, hr={}", (int)hr);
capture_lock.unlock();
if (callback_)
callback_(nullptr, ScreenCapturer::kBackendReset, 0, 0, "", nullptr);
RecreateDuplicationForCurrentMonitor();
continue;
}
@@ -356,9 +393,42 @@ void ScreenCapturerDxgi::CaptureLoop() {
continue;
}
auto pixels = static_cast<const uint8_t*>(mapped.pData);
int stride = static_cast<int>(mapped.RowPitch);
int logical_width = static_cast<int>(src_desc.Width);
int logical_height = static_cast<int>(src_desc.Height);
libyuv::RotationMode rotation = libyuv::kRotate0;
switch (rotation_) {
case DXGI_MODE_ROTATION_ROTATE90:
rotation = libyuv::kRotate90;
break;
case DXGI_MODE_ROTATION_ROTATE180:
rotation = libyuv::kRotate180;
break;
case DXGI_MODE_ROTATION_ROTATE270:
rotation = libyuv::kRotate270;
break;
default:
break;
}
if (rotation != libyuv::kRotate0) {
const bool swap_axes = rotation != libyuv::kRotate180;
const int rotated_width = swap_axes ? logical_height : logical_width;
rotated_frame.resize(static_cast<size_t>(logical_width) * logical_height *
4);
if (libyuv::ARGBRotate(pixels, stride, rotated_frame.data(),
rotated_width * 4, logical_width, logical_height,
rotation) != 0) {
d3d_context_->Unmap(staging_.Get(), 0);
duplication_->ReleaseFrame();
continue;
}
pixels = rotated_frame.data();
stride = rotated_width * 4;
if (swap_axes) std::swap(logical_width, logical_height);
}
int even_width = logical_width & ~1;
int even_height = static_cast<int>(src_desc.Height) & ~1;
int even_height = logical_height & ~1;
if (even_width <= 0 || even_height <= 0) {
d3d_context_->Unmap(staging_.Get(), 0);
duplication_->ReleaseFrame();
@@ -374,25 +444,29 @@ void ScreenCapturerDxgi::CaptureLoop() {
nv12_height_ = even_height;
}
libyuv::ARGBToNV12(static_cast<const uint8_t*>(mapped.pData),
static_cast<int>(mapped.RowPitch), nv12_frame_,
even_width, nv12_frame_ + even_width * even_height,
even_width, even_width, even_height);
const int converted =
libyuv::ARGBToNV12(pixels, stride, nv12_frame_, even_width,
nv12_frame_ + even_width * even_height, even_width,
even_width, even_height);
if (callback_) {
int idx = monitor_index_.load();
cached_generation = duplication_generation_;
d3d_context_->Unmap(staging_.Get(), 0);
duplication_->ReleaseFrame();
capture_lock.unlock();
if (converted == 0 && frame_monitor == monitor_index_.load() && callback_) {
int idx = frame_monitor;
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
const std::string stream_id = MakeDisplayStreamId(idx);
callback_(nv12_frame_, nv12_size, even_width, even_height,
stream_id.c_str(), nullptr);
cached_monitor = idx;
cached_frame_valid = true;
} else {
LOG_ERROR("DXGI: CaptureLoop invalid monitor_index {} (list size {})",
idx, display_info_list_.size());
}
}
d3d_context_->Unmap(staging_.Get(), 0);
duplication_->ReleaseFrame();
}
}
@@ -63,6 +63,8 @@ class ScreenCapturerDxgi : public ScreenCapturer {
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d_context_;
Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_;
DXGI_MODE_ROTATION rotation_ = DXGI_MODE_ROTATION_IDENTITY;
uint64_t duplication_generation_ = 0;
std::atomic<bool> running_{false};
std::atomic<bool> paused_{false};
@@ -351,6 +351,10 @@ int ScreenCapturerWgc::ResetToInitialMonitor() {
}
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
int id) {
if (!frame.data && frame.width == 0 && frame.height == 0) {
if (on_data_) on_data_(nullptr, ScreenCapturer::kBackendReset, 0, 0, "", nullptr);
return;
}
if (!running_ || !on_data_) {
return;
}
@@ -127,6 +127,13 @@ class WgcPluginCapturer final : public ScreenCapturer {
DestroyFn destroy_fn_ = nullptr;
};
const char* CaptureBackendName(const ScreenCapturer* capturer) {
if (dynamic_cast<const ScreenCapturerDxgi*>(capturer)) return "DXGI";
if (dynamic_cast<const WgcPluginCapturer*>(capturer)) return "WGC";
if (dynamic_cast<const ScreenCapturerGdi*>(capturer)) return "GDI";
return "unavailable";
}
std::string BuildSecureCaptureCommand(int left, int top, int width, int height,
bool show_cursor,
const std::string& stage,
@@ -330,7 +337,13 @@ bool QuerySecureDesktopHelperFrame(
ScreenCapturerWin::ScreenCapturerWin() {}
ScreenCapturerWin::~ScreenCapturerWin() { Destroy(); }
void ScreenCapturerWin::NotifyPrivacyCapture(bool running) {
if (privacy_)
privacy_->CaptureChanged(CaptureBackendName(impl_.get()), running);
}
int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
NotifyPrivacyCapture(false);
fps_ = fps;
cb_orig_ = cb;
native_output_logged_.store(false, std::memory_order_relaxed);
@@ -344,12 +357,17 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
cb_ = [this](unsigned char* data, int size, int w, int h,
const char* reported_stream_id,
const MiniRtcNativeVideoFrame* native_frame) {
if (size == ScreenCapturer::kBackendReset) {
if (privacy_) privacy_->CaptureInterrupted();
return;
}
if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) {
return;
}
const char* raw_stream_id = reported_stream_id ? reported_stream_id : "";
std::string mapped_stream_id;
DisplayInfo privacy_display{"", 0, 0, 0, 0};
{
std::lock_guard<std::mutex> lock(alias_mutex_);
auto it = stream_id_alias_.find(raw_stream_id);
@@ -363,6 +381,21 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
mapped_stream_id = ResolveDisplayStreamId(
mapped_stream_id.c_str(), canonical_displays_.size(),
monitor_index_.load(std::memory_order_relaxed));
for (size_t index = 0; index < canonical_displays_.size(); ++index) {
if (mapped_stream_id == MakeDisplayStreamId(index)) {
privacy_display = canonical_displays_[index];
break;
}
}
}
if (privacy_) {
if (privacy_->Engaged() && !IsWindowsPrivacyDesktopAvailable()) {
privacy_->Fail("Secure or unavailable desktop; privacy cannot cover system security UI. Remote operation paused.");
return;
}
if (!privacy_->ObserveFrame(data, size > 0 ? static_cast<size_t>(size) : 0,
w, h, privacy_display.left, privacy_display.top,
privacy_display.width, privacy_display.height)) return;
}
if (mapped_stream_id.empty()) {
if (!invalid_stream_id_logged_.exchange(true,
@@ -393,42 +426,23 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
native_frame);
};
impl_.reset();
int ret = -1;
impl_ = WgcPluginCapturer::Create();
impl_is_wgc_plugin_ = (impl_ != nullptr);
ret = impl_ ? impl_->Init(fps_, cb_) : -1;
if (ret == 0) {
LOG_INFO("Windows capturer: using WGC plugin");
BuildCanonicalFromImpl();
monitor_index_.store(0, std::memory_order_relaxed);
initial_monitor_index_ = 0;
return 0;
}
LOG_WARN("Windows capturer: WGC plugin init failed (ret={}), try DXGI", ret);
impl_.reset();
impl_is_wgc_plugin_ = false;
impl_ = std::make_unique<ScreenCapturerDxgi>();
impl_is_wgc_plugin_ = false;
ret = impl_->Init(fps_, cb_);
if (ret == 0) {
LOG_INFO("Windows capturer: using DXGI Desktop Duplication");
BuildCanonicalFromImpl();
monitor_index_.store(0, std::memory_order_relaxed);
initial_monitor_index_ = 0;
return 0;
}
LOG_WARN("Windows capturer: DXGI init failed (ret={}), fallback to GDI", ret);
impl_.reset();
impl_ = std::make_unique<ScreenCapturerGdi>();
impl_is_wgc_plugin_ = false;
ret = impl_->Init(fps_, cb_);
if (ret == 0) {
LOG_INFO("Windows capturer: using GDI BitBlt");
auto try_init = [&](std::unique_ptr<ScreenCapturer> candidate) {
ret = candidate ? candidate->Init(fps_, cb_) : -1;
if (ret != 0) {
LOG_WARN("Windows capturer: {} init failed (ret={})",
CaptureBackendName(candidate.get()), ret);
return false;
}
impl_ = std::move(candidate);
return true;
};
// Short-circuiting also keeps the unused WGC plugin unloaded.
if (try_init(std::make_unique<ScreenCapturerDxgi>()) ||
try_init(WgcPluginCapturer::Create()) ||
try_init(std::make_unique<ScreenCapturerGdi>())) {
LOG_INFO("Windows capturer: using {}", CaptureBackendName(impl_.get()));
BuildCanonicalFromImpl();
monitor_index_.store(0, std::memory_order_relaxed);
initial_monitor_index_ = 0;
@@ -436,7 +450,6 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
}
LOG_ERROR("Windows capturer: all implementations failed, ret={}", ret);
impl_.reset();
return -1;
}
@@ -446,7 +459,6 @@ int ScreenCapturerWin::Destroy() {
if (impl_) {
impl_->Destroy();
impl_.reset();
impl_is_wgc_plugin_ = false;
}
{
std::lock_guard<std::mutex> lock(alias_mutex_);
@@ -459,8 +471,15 @@ int ScreenCapturerWin::Destroy() {
void ScreenCapturerWin::EmitCapturedFrame(
unsigned char* data, int size, int width, int height,
const char* stream_id, const MiniRtcNativeVideoFrame* native_frame) {
if (!cb_orig_) {
const char* stream_id, const MiniRtcNativeVideoFrame* native_frame,
bool from_secure_desktop) {
// A helper IPC already in flight when enable began must not bypass the
// verified ordinary-desktop callback when it completes later.
if (from_secure_desktop && privacy_ && privacy_->Engaged()) {
privacy_->Fail("Secure desktop helper frame arrived during privacy; remote operation paused");
return;
}
if (!cb_orig_ || (privacy_ && !privacy_->RemoteAllowed())) {
return;
}
@@ -499,11 +518,43 @@ void ScreenCapturerWin::EmitCapturedFrame(
owned_frame->Release();
}
void ScreenCapturerWin::RestoreMonitor(int monitor_index) {
RebuildAliasesFromImpl();
if (monitor_index > 0 && impl_->SwitchTo(monitor_index) != 0) {
monitor_index_.store(0, std::memory_order_relaxed);
}
}
bool ScreenCapturerWin::TryStartBackend(
std::unique_ptr<ScreenCapturer> candidate, int monitor_index) {
if (!candidate) {
LOG_WARN("Windows capturer: WGC plugin unavailable");
return false;
}
const char* name = CaptureBackendName(candidate.get());
int ret = candidate->Init(fps_, cb_);
if (ret == 0)
ret = candidate->Start(show_cursor_.load(std::memory_order_relaxed));
if (ret != 0) {
LOG_WARN("Windows capturer: {} initialization/start failed (ret={})", name,
ret);
candidate->Destroy();
return false;
}
// Commit only after the replacement is running. Failed candidates release
// their resources without replacing the current backend.
impl_ = std::move(candidate);
RestoreMonitor(monitor_index);
LOG_INFO("Windows capturer: started {}", name);
return true;
}
int ScreenCapturerWin::Start(bool show_cursor) {
if (!impl_) return -1;
if (running_.load(std::memory_order_relaxed)) {
return 0;
}
NotifyPrivacyCapture(false);
show_cursor_.store(show_cursor, std::memory_order_relaxed);
paused_.store(false, std::memory_order_relaxed);
@@ -528,37 +579,15 @@ int ScreenCapturerWin::Start(bool show_cursor) {
LOG_WARN("Windows capturer: refresh/start failed (ret={}), trying fallback",
ret);
auto try_init_start = [&](std::unique_ptr<ScreenCapturer> cand) -> bool {
int r = cand->Init(fps_, cb_);
if (r != 0) return false;
int s = cand->Start(show_cursor);
if (s == 0) {
impl_ = std::move(cand);
impl_is_wgc_plugin_ = false;
RebuildAliasesFromImpl();
if (requested_monitor > 0 &&
impl_->SwitchTo(requested_monitor) != 0) {
monitor_index_.store(0, std::memory_order_relaxed);
}
return true;
}
return false;
};
bool fallback_started = false;
if (impl_is_wgc_plugin_) {
if (try_init_start(std::make_unique<ScreenCapturerDxgi>())) {
LOG_INFO("Windows capturer: fallback to DXGI");
fallback_started = true;
} else if (try_init_start(std::make_unique<ScreenCapturerGdi>())) {
LOG_INFO("Windows capturer: fallback to GDI");
fallback_started = true;
}
} else if (dynamic_cast<ScreenCapturerDxgi*>(impl_.get())) {
if (try_init_start(std::make_unique<ScreenCapturerGdi>())) {
LOG_INFO("Windows capturer: fallback to GDI");
fallback_started = true;
}
if (dynamic_cast<ScreenCapturerDxgi*>(impl_.get()) &&
TryStartBackend(WgcPluginCapturer::Create(), requested_monitor)) {
fallback_started = true;
}
if (!fallback_started && (dynamic_cast<WgcPluginCapturer*>(impl_.get()) ||
dynamic_cast<ScreenCapturerDxgi*>(impl_.get()))) {
fallback_started = TryStartBackend(std::make_unique<ScreenCapturerGdi>(),
requested_monitor);
}
if (!fallback_started) {
@@ -568,6 +597,7 @@ int ScreenCapturerWin::Start(bool show_cursor) {
}
running_.store(true, std::memory_order_relaxed);
NotifyPrivacyCapture(true);
secure_desktop_capture_active_.store(false, std::memory_order_relaxed);
post_secure_desktop_waiting_for_frame_.store(false,
std::memory_order_relaxed);
@@ -581,6 +611,7 @@ int ScreenCapturerWin::Start(bool show_cursor) {
}
int ScreenCapturerWin::Stop() {
NotifyPrivacyCapture(false);
running_.store(false, std::memory_order_relaxed);
secure_desktop_capture_active_.store(false, std::memory_order_relaxed);
post_secure_desktop_waiting_for_frame_.store(false,
@@ -610,9 +641,12 @@ int ScreenCapturerWin::Resume(int monitor_index) {
int ScreenCapturerWin::SwitchTo(int monitor_index) {
if (!impl_) return -1;
if (privacy_) privacy_->DisplayChanging();
const int ret = impl_->SwitchTo(monitor_index);
if (ret == 0) {
monitor_index_.store(monitor_index, std::memory_order_relaxed);
} else if (privacy_) {
privacy_->Fail("Display switch failed; remote operation paused");
}
return ret;
}
@@ -735,53 +769,19 @@ void ScreenCapturerWin::StopSecureCaptureThread() {
}
bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
NotifyPrivacyCapture(false);
if (!impl_ || !running_.load(std::memory_order_relaxed)) {
return false;
}
const bool show_cursor = show_cursor_.load(std::memory_order_relaxed);
const int current_monitor = monitor_index_.load(std::memory_order_relaxed);
auto restore_monitor = [&]() {
RebuildAliasesFromImpl();
if (current_monitor > 0 && impl_->SwitchTo(current_monitor) != 0) {
monitor_index_.store(0, std::memory_order_relaxed);
}
};
auto try_started_backend = [&](std::unique_ptr<ScreenCapturer> cand,
const char* name,
bool is_wgc_plugin) -> bool {
if (!cand) {
return false;
}
const int init_ret = cand->Init(fps_, cb_);
if (init_ret != 0) {
LOG_WARN("Windows capturer: {} init after secure desktop failed (ret={})",
name, init_ret);
return false;
}
const int start_ret = cand->Start(show_cursor);
if (start_ret != 0) {
LOG_WARN(
"Windows capturer: {} start after secure desktop failed (ret={})",
name, start_ret);
cand->Destroy();
return false;
}
if (impl_) {
impl_->Destroy();
}
impl_ = std::move(cand);
impl_is_wgc_plugin_ = is_wgc_plugin;
restore_monitor();
LOG_INFO("Windows capturer: restarted {} after secure desktop", name);
return true;
};
LOG_INFO("Windows capturer: restarting capture backend after secure desktop");
impl_->Stop();
int ret = impl_->Start(show_cursor);
if (ret == 0) {
restore_monitor();
RestoreMonitor(current_monitor);
return true;
}
@@ -795,20 +795,14 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
ret = impl_->Start(show_cursor);
}
if (ret == 0) {
restore_monitor();
RestoreMonitor(current_monitor);
return true;
}
if (impl_is_wgc_plugin_ &&
try_started_backend(WgcPluginCapturer::Create(), "WGC plugin", true)) {
return true;
}
if (try_started_backend(std::make_unique<ScreenCapturerDxgi>(), "DXGI",
false)) {
return true;
}
if (try_started_backend(std::make_unique<ScreenCapturerGdi>(), "GDI",
false)) {
if (TryStartBackend(std::make_unique<ScreenCapturerDxgi>(),
current_monitor) ||
TryStartBackend(WgcPluginCapturer::Create(), current_monitor) ||
TryStartBackend(std::make_unique<ScreenCapturerGdi>(), current_monitor)) {
return true;
}
@@ -1123,6 +1117,14 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
std::vector<uint8_t> secure_frame;
while (running_.load(std::memory_order_relaxed)) {
// A privacy session must NEVER continue using the helper's secure-desktop
// GDI frames. Its HWNDs belong to the ordinary interactive desktop only.
if (privacy_ && privacy_->Engaged()) {
if (!IsWindowsPrivacyDesktopAvailable())
privacy_->Fail("Secure desktop or lock screen; remote operation paused. Privacy cannot cover Windows security UI.");
std::this_thread::sleep_for(std::chrono::milliseconds(25));
continue;
}
if (paused_.load(std::memory_order_relaxed)) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
@@ -1295,7 +1297,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
if (frame_delivered && !secure_frame.empty()) {
EmitCapturedFrame(secure_frame.data(),
static_cast<int>(secure_frame.size()), captured_width,
captured_height, display_name.c_str());
captured_height, display_name.c_str(), nullptr, true);
}
if (!frame_delivered && !frame_pending) {
@@ -20,6 +20,7 @@
#include <vector>
#include "screen_capturer.h"
#include "privacy_controller.h"
namespace crossdesk {
@@ -43,10 +44,12 @@ class ScreenCapturerWin : public ScreenCapturer {
int ResetToInitialMonitor() override;
std::vector<DisplayInfo> GetDisplayInfoList() override;
void SetPrivacyController(PrivacyController* privacy) { privacy_ = privacy; }
private:
std::unique_ptr<ScreenCapturer> impl_;
bool impl_is_wgc_plugin_ = false;
PrivacyController* privacy_ = nullptr;
void NotifyPrivacyCapture(bool running);
int fps_ = 60;
cb_desktop_data cb_;
cb_desktop_data cb_orig_;
@@ -86,9 +89,13 @@ class ScreenCapturerWin : public ScreenCapturer {
void BuildCanonicalFromImpl();
void RebuildAliasesFromImpl();
void RestoreMonitor(int monitor_index);
bool TryStartBackend(std::unique_ptr<ScreenCapturer> candidate,
int monitor_index);
void EmitCapturedFrame(unsigned char* data, int size, int width, int height,
const char* stream_id,
const MiniRtcNativeVideoFrame* native_frame = nullptr);
const MiniRtcNativeVideoFrame* native_frame = nullptr,
bool from_secure_desktop = false);
void StopSecureCaptureThread();
bool RestartCaptureBackendAfterSecureDesktop();
void SecureDesktopCaptureLoop();
@@ -339,6 +339,7 @@ void WgcSessionImpl::OnClosed(
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
winrt::Windows::Foundation::IInspectable const&) {
std::lock_guard locker(lock_);
if (observer_) observer_->OnFrame({}, id_);
const bool was_running = is_running_;
const bool was_paused = is_paused_;
try {