[fix] improve Windows secure desktop capture and input transitions

This commit is contained in:
kunkundi
2026-10-09 00:05:37 +08:00
parent 5004c91a6c
commit 3cfe7cbb80
26 changed files with 1376 additions and 582 deletions
@@ -408,6 +408,7 @@ SecureDesktopServiceStatus ReadSecureDesktopServiceStatus() {
}
status.active_session_id = json.value("active_session_id", 0xFFFFFFFFu);
status.consent_ui_visible = json.value("consent_ui_visible", false);
status.helper_running = json.value("secure_input_helper_running", false);
status.helper_process_id = json.value("secure_input_helper_pid", 0u);
status.interactive_stage = json.value("interactive_stage", std::string());
@@ -425,11 +426,7 @@ SecureDesktopServiceStatus ReadSecureDesktopServiceStatus() {
SecureDesktopServiceStatus QuerySecureDesktopServiceStatus() {
auto status = ReadSecureDesktopServiceStatus();
if (!status.service_available && IsCurrentSessionUserDesktopActive()) {
status.desktop_state_known = true;
status.capture_active = false;
status.interactive_stage = "user-desktop";
}
if (IsCurrentSessionUserDesktopActive()) status.OnUserDesktopObserved();
return status;
}
@@ -445,10 +442,11 @@ bool QuerySecureDesktopHelperCommand(DWORD session_id,
GetCrossDeskSecureInputHelperPipeName(session_id);
std::string error;
DWORD code = 0;
// The legacy single-frame path includes a full GDI capture and a large
// response. Give it a separate deadline from the small control messages.
// Starting may have to join/release the old GPU producer first. Its reply
// cannot use the short deadline intended for lightweight control messages.
const DWORD timeout_ms =
command.rfind(kCrossDeskSecureInputCaptureCommandPrefix, 0) == 0
(command.rfind(kCrossDeskSecureInputCaptureCommandPrefix, 0) == 0 ||
command.rfind(kCrossDeskSecureInputCaptureStartCommandPrefix, 0) == 0)
? 1000
: kSecureDesktopHelperPipeTimeoutMs;
const bool ok = QueryNamedPipeWithDeadline(pipe_name, command, timeout_ms,
@@ -579,8 +577,6 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
0, std::memory_order_relaxed);
const ULONGLONG elapsed_ms =
start_tick == 0 ? 0 : GetTickCount64() - start_tick;
post_secure_desktop_drop_logged_.store(false,
std::memory_order_relaxed);
LOG_INFO(
"Windows capturer first normal frame after secure desktop: "
"reported_stream='{}', mapped_stream='{}', size={}x{}, bytes={}, "
@@ -804,7 +800,6 @@ void ScreenCapturerWin::ResetPostSecureDesktopState() {
secure_desktop_capture_active_.store(false, std::memory_order_relaxed);
post_secure_desktop_waiting_for_frame_.store(false,
std::memory_order_relaxed);
post_secure_desktop_drop_logged_.store(false, std::memory_order_relaxed);
post_secure_desktop_started_tick_.store(0, std::memory_order_relaxed);
SharedSecureDesktopCursorState().SetActive(false);
}
@@ -1237,6 +1232,7 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
LOG_INFO("Windows capturer: restarting capture backend after secure desktop");
impl_->Stop();
last_capture_progress_tick_.store(GetTickCount64(), std::memory_order_relaxed);
int ret = impl_->Start(show_cursor);
if (ret == 0) {
applied_show_cursor_ = show_cursor;
@@ -1403,7 +1399,7 @@ void ScreenCapturerWin::StopSecureDesktopSharedCapture(DWORD session_id) {
target_session_id = secure_shared_session_id_;
}
if (secure_shared_capture_started_ &&
if ((secure_shared_capture_started_ || secure_shared_start_pending_) &&
target_session_id != 0xFFFFFFFF) {
std::vector<uint8_t> response;
std::string error_message;
@@ -1414,15 +1410,9 @@ void ScreenCapturerWin::StopSecureDesktopSharedCapture(DWORD session_id) {
CloseSecureDesktopSharedFrame();
secure_shared_capture_started_ = false;
secure_shared_start_pending_ = false;
secure_shared_session_id_ = 0xFFFFFFFF;
secure_shared_left_ = 0;
secure_shared_top_ = 0;
secure_shared_width_ = 0;
secure_shared_height_ = 0;
secure_shared_fps_ = 0;
secure_shared_show_cursor_ = true;
secure_shared_stage_.clear();
secure_shared_desktop_.clear();
secure_shared_configuration_ = {};
}
bool ScreenCapturerWin::OpenSecureDesktopSharedFrame(DWORD session_id,
@@ -1569,27 +1559,37 @@ bool ScreenCapturerWin::StartSecureDesktopSharedCapture(
return false;
}
const SharedCaptureConfiguration configuration{left, top, width, height,
show_cursor, fps};
if (secure_shared_capture_started_ &&
secure_shared_session_id_ == session_id &&
secure_shared_left_ == left && secure_shared_top_ == top &&
secure_shared_width_ == width && secure_shared_height_ == height &&
secure_shared_stage_ == stage && secure_shared_desktop_ == desktop &&
secure_shared_show_cursor_ == show_cursor && secure_shared_fps_ == fps &&
secure_shared_configuration_ == configuration &&
OpenSecureDesktopSharedFrame(session_id, mapping_size, error_out)) {
return true;
}
StopSecureDesktopSharedCapture(secure_shared_session_id_);
if (secure_shared_session_id_ != session_id) {
StopSecureDesktopSharedCapture(secure_shared_session_id_);
} else {
// capture-start performs the replacement atomically in the helper. Avoid
// a separate stop round trip and keep retries idempotent after a timeout.
CloseSecureDesktopSharedFrame();
}
secure_shared_capture_started_ = false;
secure_shared_start_pending_ = true;
secure_shared_session_id_ = session_id;
const std::string command =
BuildSecureCaptureStartCommand(left, top, width, height, show_cursor, fps,
stage, desktop);
const std::string command = BuildSecureCaptureStartCommand(
left, top, width, height, show_cursor, fps, stage, desktop);
std::vector<uint8_t> response;
if (!QuerySecureDesktopHelperCommand(session_id, command, &response,
error_out)) {
secure_shared_start_pending_ =
error_out && IsPendingSecureCaptureStart(*error_out);
return false;
}
secure_shared_start_pending_ = false;
Json json = Json::parse(response.begin(), response.end(), nullptr, false);
if (json.is_discarded() || !json.value("ok", false)) {
if (error_out != nullptr) {
@@ -1600,14 +1600,7 @@ bool ScreenCapturerWin::StartSecureDesktopSharedCapture(
secure_shared_capture_started_ = true;
secure_shared_session_id_ = session_id;
secure_shared_left_ = left;
secure_shared_top_ = top;
secure_shared_width_ = width;
secure_shared_height_ = height;
secure_shared_show_cursor_ = show_cursor;
secure_shared_fps_ = fps;
secure_shared_stage_ = stage;
secure_shared_desktop_ = desktop;
secure_shared_configuration_ = configuration;
if (!OpenSecureDesktopSharedFrame(session_id, mapping_size, error_out)) {
StopSecureDesktopSharedCapture(session_id);
@@ -1628,6 +1621,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
ULONGLONG capture_stage_started_tick = 0;
bool post_secure_restart_pending = false;
bool desktop_was_unavailable = false;
bool normal_backend_suspended = false;
ULONGLONG post_secure_restart_deadline_tick = 0;
ULONGLONG last_post_secure_restart_tick = 0;
ULONGLONG next_cursor_update_tick = 0;
@@ -1685,9 +1679,14 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
const ULONGLONG now = GetTickCount64();
const auto frame_started = std::chrono::steady_clock::now();
report_stats(false);
if (auto sample = status_poller.Take()) {
const auto previous = std::exchange(status, std::move(sample->status));
max_status_ms = (std::max)(max_status_ms, sample->query_ms);
auto sample = status_poller.Take();
if (sample || (desktop_available && status.capture_active)) {
auto latest = sample ? std::move(sample->status) : status;
// Recheck when applying a queued reply, not only when querying. A local
// unlock must never be undone by a delayed secure-desktop snapshot.
if (desktop_available) latest.OnUserDesktopObserved();
const auto previous = std::exchange(status, std::move(latest));
if (sample) max_status_ms = (std::max)(max_status_ms, sample->query_ms);
if (status.service_available != previous.service_available ||
status.error != previous.error) {
if (status.service_available) {
@@ -1718,8 +1717,12 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
report_stats(true);
// A restarted helper can leave our mapping/event handles alive, but
// their old producer is gone. Explicitly establish a fresh stream.
StopSecureDesktopSharedCapture(secure_shared_session_id_);
frame_schedule.Reset(now);
if (status.capture_active != previous.capture_active ||
status.active_session_id != previous.active_session_id ||
status.helper_process_id != previous.helper_process_id) {
StopSecureDesktopSharedCapture(secure_shared_session_id_);
frame_schedule.Reset(now);
}
const bool secure_capture_started =
!previous.capture_active && status.capture_active;
const bool secure_capture_ended =
@@ -1734,8 +1737,6 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
post_secure_restart_pending = false;
post_secure_desktop_waiting_for_frame_.store(
false, std::memory_order_relaxed);
post_secure_desktop_drop_logged_.store(false,
std::memory_order_relaxed);
post_secure_desktop_started_tick_.store(0, std::memory_order_relaxed);
} else if (secure_capture_ended) {
post_secure_restart_pending = true;
@@ -1744,8 +1745,6 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
last_post_secure_restart_tick = 0;
post_secure_desktop_waiting_for_frame_.store(
true, std::memory_order_relaxed);
post_secure_desktop_drop_logged_.store(false,
std::memory_order_relaxed);
post_secure_desktop_started_tick_.store(now,
std::memory_order_relaxed);
}
@@ -1763,28 +1762,52 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
last_post_secure_restart_tick = 0;
}
if ((!desktop_available || status.capture_active) &&
!normal_backend_suspended) {
// LockApp may return to Default while the session remains locked. Merely
// dropping callbacks leaves a second DXGI/GDI/WGC pipeline capturing and
// converting the same desktop alongside the helper. Stop it once, also
// when local lock detection precedes the service/helper startup.
std::lock_guard<std::mutex> lock(impl_mutex_);
if (impl_) {
impl_->Stop();
normal_backend_suspended = true;
LOG_INFO("Windows capturer: normal backend suspended for secure desktop");
}
}
if (!status.capture_active || status.active_session_id == 0xFFFFFFFF) {
StopSecureDesktopSharedCapture(secure_shared_session_id_);
CheckDisplayPresence(now);
MaybeAdoptVirtualDisplay();
CheckCaptureProgress(now);
// Normal capture was intentionally unavailable on the secure desktop.
// Do not schedule a second topology/stall rebuild during its recovery.
if (!post_secure_restart_pending && desktop_available &&
!normal_backend_suspended) {
CheckDisplayPresence(now);
MaybeAdoptVirtualDisplay();
CheckCaptureProgress(now);
}
if (post_secure_restart_pending && desktop_available) {
if (now >= post_secure_restart_deadline_tick) {
LOG_WARN(
"Windows capturer: capture backend restart after secure desktop "
"timed out");
post_secure_restart_pending = false;
"still unavailable; retrying");
// The normal backend is stopped. Retain recovery ownership instead
// of falling back to a stall probe on a thread that is not running.
post_secure_restart_deadline_tick =
now + kPostSecureDesktopRestartTimeoutMs;
} else if (last_post_secure_restart_tick == 0 ||
now - last_post_secure_restart_tick >=
kPostSecureDesktopRestartRetryMs) {
last_post_secure_restart_tick = now;
post_secure_restart_pending =
!RestartCaptureBackendAfterSecureDesktop();
if (!post_secure_restart_pending) normal_backend_suspended = false;
if (!post_secure_restart_pending && privacy_)
privacy_->ResumeAfterDesktopRecovery();
}
}
if (!post_secure_restart_pending && now >= next_cursor_update_tick) {
if (!post_secure_restart_pending && !normal_backend_suspended &&
now >= next_cursor_update_tick) {
next_cursor_update_tick = now + 500;
ApplyCursorCaptureSetting();
}
@@ -1835,7 +1858,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
status.interactive_stage, status.interactive_desktop, show_cursor,
shared_fps, &error_message);
if (!shared_ready) {
report_shared_error("start", error_message);
if (!secure_shared_start_pending_) report_shared_error("start", error_message);
frame_schedule.OnStartFailure(GetTickCount64());
} else if (!was_started) {
frame_schedule.Reset(GetTickCount64());
@@ -1851,8 +1874,8 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
frame_schedule.OnSharedFrame(GetTickCount64());
}
const bool frame_pending = shared_ready && !frame_delivered &&
IsPendingSecureDesktopFrame(error_message);
const bool frame_pending = secure_shared_start_pending_ ||
(shared_ready && !frame_delivered && IsPendingSecureDesktopFrame(error_message));
if (frame_pending) ++shared_waits;
if (shared_ready && !frame_delivered && !frame_pending) {
report_shared_error("read", error_message);
@@ -1905,6 +1928,10 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
}
}
// The shared producer owns frame pacing. Waiting again here can miss the
// next publication (and overwrite a pending frame) due to timer rounding.
if (shared_ready && (frame_delivered || frame_pending)) continue;
const auto elapsed_ms =
std::chrono::duration_cast<std::chrono::milliseconds>(
std::chrono::steady_clock::now() - frame_started)
@@ -25,6 +25,7 @@
#include "privacy_controller.h"
// Relative: platform include directories stay private to the capture target.
#include "../virtual_display/virtual_display_provisioner.h"
#include "../service/shared_capture_configuration.h"
#include "headless_recovery.h"
namespace crossdesk {
@@ -117,7 +118,6 @@ class ScreenCapturerWin : public ScreenCapturer {
std::atomic<int> monitor_index_{0};
std::atomic<bool> secure_desktop_capture_active_{false};
std::atomic<bool> post_secure_desktop_waiting_for_frame_{false};
std::atomic<bool> post_secure_desktop_drop_logged_{false};
std::atomic<ULONGLONG> post_secure_desktop_started_tick_{0};
std::thread secure_capture_thread_;
HANDLE secure_frame_mapping_ = nullptr;
@@ -125,15 +125,9 @@ class ScreenCapturerWin : public ScreenCapturer {
uint8_t* secure_frame_view_ = nullptr;
size_t secure_frame_view_size_ = 0;
DWORD secure_shared_session_id_ = 0xFFFFFFFF;
int secure_shared_left_ = 0;
int secure_shared_top_ = 0;
int secure_shared_width_ = 0;
int secure_shared_height_ = 0;
int secure_shared_fps_ = 0;
bool secure_shared_show_cursor_ = true;
std::string secure_shared_stage_;
std::string secure_shared_desktop_;
SharedCaptureConfiguration secure_shared_configuration_;
bool secure_shared_capture_started_ = false;
bool secure_shared_start_pending_ = false;
std::shared_ptr<CapturedNv12FramePool> native_frame_pool_;
void BuildCanonicalFromImpl();
@@ -0,0 +1,160 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-10-08
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SECURE_DESKTOP_DXGI_CAPTURE_H_
#define _SECURE_DESKTOP_DXGI_CAPTURE_H_
#include <Windows.h>
#include <d3d11.h>
#include <dxgi1_2.h>
#include <libyuv.h>
#include <wrl/client.h>
#include <cstdint>
#include <vector>
namespace crossdesk {
// Owned by the SYSTEM helper's capture thread, after binding its input desktop.
// Destroy before switching desktops. The uncomposited pixels stay valid on a
// timeout, so a static lock screen and a moving separate cursor keep their
// requested cadence without another GPU readback or a GDI screenshot.
class SecureDesktopDxgiCapture {
public:
HRESULT Initialize(int left, int top, int width, int height) {
if (width <= 0 || height <= 0) return E_INVALIDARG;
Microsoft::WRL::ComPtr<IDXGIFactory1> factory;
HRESULT hr = CreateDXGIFactory1(IID_PPV_ARGS(factory.GetAddressOf()));
if (FAILED(hr)) return hr;
Microsoft::WRL::ComPtr<IDXGIAdapter1> adapter;
for (UINT a = 0; SUCCEEDED(factory->EnumAdapters1(
a, adapter.ReleaseAndGetAddressOf())); ++a) {
Microsoft::WRL::ComPtr<IDXGIOutput> output;
for (UINT o = 0; SUCCEEDED(adapter->EnumOutputs(
o, output.ReleaseAndGetAddressOf())); ++o) {
DXGI_OUTPUT_DESC desc{};
if (FAILED(output->GetDesc(&desc)) || !desc.AttachedToDesktop) continue;
const RECT& bounds = desc.DesktopCoordinates;
if (left < bounds.left || top < bounds.top ||
static_cast<int64_t>(left) + width > bounds.right ||
static_cast<int64_t>(top) + height > bounds.bottom) continue;
hr = D3D11CreateDevice(
adapter.Get(), D3D_DRIVER_TYPE_UNKNOWN, nullptr,
D3D11_CREATE_DEVICE_BGRA_SUPPORT, nullptr, 0, D3D11_SDK_VERSION,
device_.GetAddressOf(), nullptr, context_.GetAddressOf());
if (FAILED(hr)) return hr;
Microsoft::WRL::ComPtr<IDXGIOutput1> output1;
hr = output.As(&output1);
if (FAILED(hr)) return hr;
hr = output1->DuplicateOutput(device_.Get(), duplication_.GetAddressOf());
if (FAILED(hr)) return hr;
output_desc_ = desc;
offset_x_ = left - bounds.left;
offset_y_ = top - bounds.top;
DXGI_OUTDUPL_DESC duplication_desc{};
duplication_->GetDesc(&duplication_desc);
switch (duplication_desc.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: rotation_ = libyuv::kRotate0; break;
}
return S_OK;
}
}
// Includes headless desktops and regions spanning more than one output.
return DXGI_ERROR_NOT_FOUND;
}
// S_FALSE means no first frame yet; the caller can use GDI until one arrives.
// On any failure the caller must discard this instance, including its cache.
HRESULT Capture() {
updated_ = false;
if (!duplication_) return E_UNEXPECTED;
Microsoft::WRL::ComPtr<IDXGIResource> resource;
DXGI_OUTDUPL_FRAME_INFO info{};
const HRESULT hr = duplication_->AcquireNextFrame(0, &info, &resource);
if (hr == DXGI_ERROR_WAIT_TIMEOUT) return pixels_.empty() ? S_FALSE : S_OK;
if (FAILED(hr)) return hr;
struct ReleaseFrame {
IDXGIOutputDuplication* duplication;
~ReleaseFrame() { duplication->ReleaseFrame(); }
} release{duplication_.Get()};
if (info.LastMouseUpdateTime.QuadPart != 0) {
cursor_embedded_ = !info.PointerPosition.Visible;
}
// Pointer-only updates leave the desktop image unchanged.
if (info.LastPresentTime.QuadPart == 0 && !pixels_.empty()) return S_OK;
Microsoft::WRL::ComPtr<ID3D11Texture2D> texture;
HRESULT result = resource.As(&texture);
if (FAILED(result)) return result;
D3D11_TEXTURE2D_DESC desc{};
texture->GetDesc(&desc);
const bool swap_axes = rotation_ == libyuv::kRotate90 ||
rotation_ == libyuv::kRotate270;
const UINT logical_width = swap_axes ? desc.Height : desc.Width;
const UINT logical_height = swap_axes ? desc.Width : desc.Height;
const RECT& bounds = output_desc_.DesktopCoordinates;
if (desc.Format != DXGI_FORMAT_B8G8R8A8_UNORM ||
logical_width != static_cast<UINT>(bounds.right - bounds.left) ||
logical_height != static_cast<UINT>(bounds.bottom - bounds.top)) {
return DXGI_ERROR_ACCESS_LOST;
}
if (!staging_) {
desc.Usage = D3D11_USAGE_STAGING;
desc.BindFlags = 0;
desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
desc.MiscFlags = 0;
result = device_->CreateTexture2D(&desc, nullptr, staging_.GetAddressOf());
if (FAILED(result)) return result;
}
context_->CopyResource(staging_.Get(), texture.Get());
D3D11_MAPPED_SUBRESOURCE mapped{};
result = context_->Map(staging_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
if (FAILED(result)) return result;
stride_ = static_cast<int>(logical_width) * 4;
pixels_.resize(static_cast<size_t>(stride_) * logical_height);
const int converted = libyuv::ARGBRotate(
static_cast<const uint8_t*>(mapped.pData), static_cast<int>(mapped.RowPitch),
pixels_.data(), stride_, static_cast<int>(desc.Width),
static_cast<int>(desc.Height), rotation_);
context_->Unmap(staging_.Get(), 0);
updated_ = converted == 0;
return converted == 0 ? S_OK : E_FAIL;
}
const uint8_t* pixels() const {
return pixels_.data() + static_cast<size_t>(offset_y_) * stride_ + offset_x_ * 4;
}
int stride() const { return stride_; }
bool updated() const { return updated_; }
const DXGI_OUTPUT_DESC& output() const { return output_desc_; }
void SetSoftwareCursor(bool software) { cursor_embedded_ = software; }
bool CursorEmbedded(const CURSORINFO& cursor) const {
return cursor_embedded_ && (cursor.flags & CURSOR_SHOWING) != 0 &&
MonitorFromPoint(cursor.ptScreenPos, MONITOR_DEFAULTTONULL) ==
output_desc_.Monitor;
}
private:
Microsoft::WRL::ComPtr<ID3D11Device> device_;
Microsoft::WRL::ComPtr<ID3D11DeviceContext> context_;
Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_;
DXGI_OUTPUT_DESC output_desc_{};
libyuv::RotationMode rotation_ = libyuv::kRotate0;
int offset_x_ = 0, offset_y_ = 0, stride_ = 0;
bool cursor_embedded_ = false;
bool updated_ = false;
std::vector<uint8_t> pixels_;
};
} // namespace crossdesk
#endif // _SECURE_DESKTOP_DXGI_CAPTURE_H_
@@ -0,0 +1,65 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-10-08
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SECURE_DESKTOP_FRAME_PACER_H_
#define _SECURE_DESKTOP_FRAME_PACER_H_
#include <Windows.h>
#include <algorithm>
#include <chrono>
#include <thread>
namespace crossdesk {
// Sleep's system-tick rounding can turn a 33ms frame into a 47ms frame.
// Request precise waits only on this capture thread, without changing the
// machine's timer resolution. Older Windows versions use a regular timer.
class SecureDesktopFramePacer {
public:
using Clock = std::chrono::steady_clock;
explicit SecureDesktopFramePacer(int fps)
: interval_(std::chrono::nanoseconds(
1000000000LL / (std::clamp)(fps > 0 ? fps : 30, 1, 1000))) {
constexpr DWORD kHighResolution = 0x00000002;
timer_ = CreateWaitableTimerExW(nullptr, nullptr, kHighResolution,
TIMER_MODIFY_STATE | SYNCHRONIZE);
if (!timer_) timer_ = CreateWaitableTimerW(nullptr, FALSE, nullptr);
}
~SecureDesktopFramePacer() {
if (timer_) CloseHandle(timer_);
}
SecureDesktopFramePacer(const SecureDesktopFramePacer&) = delete;
SecureDesktopFramePacer& operator=(const SecureDesktopFramePacer&) = delete;
void Wait(Clock::time_point frame_started) {
const auto now = Clock::now();
// Keep fractional frame periods and compensate for small scheduler delays.
// After a slow frame, resume now instead of issuing a burst of old frames.
if (deadline_ == Clock::time_point{}) deadline_ = frame_started;
deadline_ += interval_;
if (deadline_ <= now) {
deadline_ = now;
return;
}
LARGE_INTEGER due{};
const auto ticks = std::chrono::duration_cast<std::chrono::nanoseconds>(
deadline_ - now).count() / 100;
due.QuadPart = -(ticks > 0 ? ticks : 1);
if (timer_ && SetWaitableTimer(timer_, &due, 0, nullptr, nullptr, FALSE) &&
WaitForSingleObject(timer_, INFINITE) == WAIT_OBJECT_0) return;
std::this_thread::sleep_until(deadline_);
}
private:
HANDLE timer_ = nullptr;
Clock::duration interval_;
Clock::time_point deadline_{};
};
} // namespace crossdesk
#endif // _SECURE_DESKTOP_FRAME_PACER_H_
@@ -12,6 +12,12 @@
namespace crossdesk {
inline bool IsPendingSecureCaptureStart(const std::string& error) {
// Once a command was written, a lost response does not mean startup failed.
return error.rfind("pipe_read_failed:", 0) == 0 ||
error.rfind("pipe_write_failed:", 0) == 0;
}
inline bool IsPendingSecureDesktopFrame(const std::string& error) {
return error == "frame_wait_timeout" ||
error == "shared_frame_write_in_progress" ||
@@ -47,4 +53,4 @@ class SecureDesktopFrameSchedule {
} // namespace crossdesk
#endif
#endif
@@ -0,0 +1,59 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-10-08
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _SECURE_DESKTOP_NV12_CACHE_H_
#define _SECURE_DESKTOP_NV12_CACHE_H_
#include <libyuv.h>
#include <cstddef>
#include <cstdint>
#include <vector>
namespace crossdesk {
// One cache per capture desktop/resource lifetime. DXGI can report a pointer
// update or timeout without changing any pixels. Keep publishing those frames
// (and fresh cursor metadata) without converting the entire desktop again.
class SecureDesktopNv12Cache {
public:
bool Convert(const uint8_t* pixels, int stride, int width, int height,
bool pixels_changed) {
updated_ = false;
if (!pixels || width <= 0 || height <= 0 || (width & 1) || (height & 1) ||
stride < static_cast<int64_t>(width) * 4) {
valid_ = false;
return false;
}
if (valid_ && !pixels_changed && width == width_ && height == height_)
return true;
valid_ = false;
const size_t luma_size = static_cast<size_t>(width) * height;
pixels_.resize(luma_size + luma_size / 2);
if (libyuv::ARGBToNV12(pixels, stride, pixels_.data(), width,
pixels_.data() + luma_size, width, width,
height) != 0)
return false;
width_ = width;
height_ = height;
valid_ = updated_ = true;
return true;
}
bool updated() const { return updated_; }
const std::vector<uint8_t>& pixels() const { return pixels_; }
private:
bool valid_ = false;
bool updated_ = false;
int width_ = 0, height_ = 0;
std::vector<uint8_t> pixels_;
};
} // namespace crossdesk
#endif // _SECURE_DESKTOP_NV12_CACHE_H_
@@ -23,6 +23,7 @@ struct SecureDesktopServiceStatus {
bool service_available = false;
bool desktop_state_known = false;
bool capture_active = false;
bool consent_ui_visible = false;
bool helper_running = false;
uint32_t helper_process_id = 0;
uint32_t active_session_id = 0xFFFFFFFF;
@@ -31,6 +32,16 @@ struct SecureDesktopServiceStatus {
std::string interactive_desktop;
std::string error;
void OnUserDesktopObserved() {
if (consent_ui_visible) return;
desktop_state_known = true;
capture_active = false;
helper_running = false;
helper_process_id = 0;
interactive_stage = "user-desktop";
interactive_desktop.clear();
}
void UpdateFrom(SecureDesktopServiceStatus latest) {
if (latest.service_available || latest.desktop_state_known) {
*this = std::move(latest);
@@ -108,4 +119,4 @@ class SecureDesktopStatusPoller {
} // namespace crossdesk
#endif
#endif