[fix] correct WGC and GDI cursor capture and positioning

This commit is contained in:
dijunkun
2026-09-16 23:03:36 +08:00
parent d9116200fe
commit ce7a3c178e
20 changed files with 309 additions and 56 deletions
@@ -67,7 +67,6 @@ struct InteractionState {
bool start_screen_capturer_ = false;
bool screen_capturer_is_started_ = false;
bool start_keyboard_capturer_ = false;
bool show_cursor_ = false;
bool keyboard_capturer_is_started_ = false;
bool keyboard_capturer_uses_window_events_ = false;
bool foucs_on_main_window_ = false;
@@ -271,6 +271,15 @@ int SessionDeviceManager::InitializeScreenCapturer() {
return -1;
}
bool SessionDeviceManager::ShouldCaptureCursor() const {
std::shared_lock lock(owner_.connection_status_mutex_);
return std::any_of(owner_.connection_status_.begin(),
owner_.connection_status_.end(), [](const auto& entry) {
return entry.second == ConnectionStatus::Connected &&
entry.first.find("web") != std::string::npos;
});
}
int SessionDeviceManager::StartScreenCapturer() {
#ifdef __APPLE__
if (!owner_.EnsureMacScreenRecordingPermission()) {
@@ -286,8 +295,11 @@ int SessionDeviceManager::StartScreenCapturer() {
}
}
LOG_INFO("Start screen capturer, show cursor: {}", owner_.show_cursor_);
const int ret = screen_capturer_->Start(owner_.show_cursor_);
// Clear before sampling, so a change arriving after the sample remains pending.
cursor_capture_dirty_.store(false, std::memory_order_relaxed);
const bool show_cursor = ShouldCaptureCursor();
LOG_INFO("Start screen capturer, show cursor: {}", show_cursor);
const int ret = screen_capturer_->Start(show_cursor);
if (ret != 0) {
LOG_ERROR("Start screen capturer failed: {}", ret);
}
@@ -503,6 +515,15 @@ void SessionDeviceManager::PushAudio(const char *data, size_t size) {
}
void SessionDeviceManager::UpdateInteractions() {
#ifdef _WIN32
if (owner_.screen_capturer_is_started_ &&
cursor_capture_dirty_.load(std::memory_order_relaxed)) {
if (auto* windows = dynamic_cast<ScreenCapturerWin*>(screen_capturer_)) {
cursor_capture_dirty_.store(false, std::memory_order_relaxed);
windows->SetCursorCapture(ShouldCaptureCursor());
}
}
#endif
#if defined(__linux__) && !defined(__APPLE__)
const bool is_wayland_session = IsWaylandSession();
const bool stop_wayland_mouse_before_screen =
@@ -10,6 +10,7 @@
#include <SDL3/SDL.h>
#include <atomic>
#include <chrono>
#include <cstddef>
#include <cstdint>
@@ -40,6 +41,11 @@ public:
void Initialize();
void UpdateInteractions();
// Call after changing the controller map. UpdateInteractions consumes this
// notification on the UI thread before reading the map under its mutex.
void OnControllerConnectionsChanged() {
cursor_capture_dirty_.store(true, std::memory_order_relaxed);
}
void DestroyDevices();
void DestroyFactories();
@@ -84,6 +90,7 @@ private:
void ClearCapturedKeyboardInput();
bool ShouldSendCapturedFrame(std::chrono::steady_clock::time_point now,
int fps);
bool ShouldCaptureCursor() const;
void RecordCaptureCadence(std::chrono::steady_clock::time_point now, int fps,
bool from_secure_desktop);
@@ -91,6 +98,7 @@ private:
SDL_AudioStream *output_stream_ = nullptr;
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
ScreenCapturer *screen_capturer_ = nullptr;
std::atomic<bool> cursor_capture_dirty_{true};
SpeakerCaptureController speaker_capture_;
DeviceControllerFactory *device_controller_factory_ = nullptr;
MouseController *mouse_controller_ = nullptr;
@@ -140,11 +140,11 @@ void GuiRuntime::HandleServerControllerDisconnected(
}
bool has_connected_controller = false;
bool has_web_controller = false;
std::string remaining_controller_id;
{
std::unique_lock lock(connection_status_mutex_);
connection_status_.erase(remote_id);
devices_.OnControllerConnectionsChanged();
connection_host_names_.erase(remote_id);
privacy_sessions_.Disconnected(remote_id);
for (const auto& [id, status] : connection_status_) {
@@ -152,14 +152,11 @@ void GuiRuntime::HandleServerControllerDisconnected(
continue;
}
has_connected_controller = true;
has_web_controller =
has_web_controller || id.find("web") != std::string::npos;
if (remaining_controller_id.empty()) {
remaining_controller_id = id;
}
}
}
show_cursor_ = has_web_controller;
if (has_connected_controller) {
remote_client_id_ = remaining_controller_id;
return;
@@ -7,6 +7,7 @@
#include <windows.h>
#include "platform/windows/input/windows_cursor_state.h"
#include "platform/windows/screen_capturer/captured_cursor_state.h"
#include "platform/windows/screen_capturer/dxgi_cursor_state.h"
#include "platform/windows/screen_capturer/secure_desktop_cursor_state.h"
#include "runtime/cursor_position.h"
@@ -53,8 +54,9 @@ bool CursorStateProvider::Sample(const std::vector<DisplayInfo>& displays,
void* cursor_monitor = state->position_valid
? displays[state->display_id].handle
: nullptr;
const bool embedded = (info.flags & CURSOR_SHOWING) != 0 &&
!SharedDxgiCursorState().ShouldDrawCursor(true, cursor_monitor);
const bool embedded = SharedCapturedCursorState().IsEmbedded(cursor_monitor) ||
((info.flags & CURSOR_SHOWING) != 0 &&
!SharedDxgiCursorState().ShouldDrawCursor(true, cursor_monitor));
impl_->cursor.Sample(info, embedded, state);
return true;
}
@@ -419,6 +419,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
}
}
runtime->connection_status_[remote_id] = status;
runtime->devices_.OnControllerConnectionsChanged();
}
switch (status) {
@@ -485,16 +486,6 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
runtime->devices_.StartSpeakerCapturer();
runtime->remote_client_id_ = remote_id;
runtime->start_mouse_controller_ = true;
{
std::shared_lock lock(runtime->connection_status_mutex_);
if (std::all_of(runtime->connection_status_.begin(),
runtime->connection_status_.end(),
[](const auto& kv) {
return kv.first.find("web") != std::string::npos;
})) {
runtime->show_cursor_ = true;
}
}
break;
}
@@ -24,14 +24,14 @@ class WindowsCursorState {
public:
void Sample(const CURSORINFO& info, bool embedded, CursorState* state) {
const bool showing = (info.flags & CURSOR_SHOWING) != 0;
state->render_mode = showing ? (embedded ? CursorRenderMode::embedded
: CursorRenderMode::separate)
: CursorRenderMode::hidden;
state->render_mode = embedded ? CursorRenderMode::embedded
: showing ? CursorRenderMode::separate
: CursorRenderMode::hidden;
state->visible = state->render_mode == CursorRenderMode::separate;
state->shape = state->visible ? ShapeFromWindowsCursor(info.hCursor)
: RemoteCursorShape::none;
state->hidden_reason = CursorHiddenReason::unspecified;
if (!showing) {
if (!showing && !embedded) {
state->hidden_reason = (info.flags & CURSOR_SUPPRESSED)
? CursorHiddenReason::system_suppressed
: info.hCursor && NoPointingDevice()
@@ -83,4 +83,4 @@ class WindowsCursorState {
} // namespace crossdesk
#endif
#endif
@@ -0,0 +1,55 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-09-16
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _CAPTURED_CURSOR_STATE_H_
#define _CAPTURED_CURSOR_STATE_H_
#include <atomic>
namespace crossdesk {
// Metadata belongs to the frame callback, not to the requested capture mode.
// WGC's DLL adapter copies its frame metadata into this executable-local scope.
inline thread_local bool current_frame_has_cursor = false;
class CapturedCursorFrameScope {
public:
explicit CapturedCursorFrameScope(bool embedded)
: previous_(current_frame_has_cursor) {
current_frame_has_cursor = embedded;
}
~CapturedCursorFrameScope() { current_frame_has_cursor = previous_; }
CapturedCursorFrameScope(const CapturedCursorFrameScope&) = delete;
CapturedCursorFrameScope& operator=(const CapturedCursorFrameScope&) = delete;
private:
bool previous_;
};
class CapturedCursorState {
public:
void Update(bool embedded, void* monitor) {
embedded_monitor_.store(embedded ? monitor : nullptr,
std::memory_order_relaxed);
}
void Reset() { Update(false, nullptr); }
bool IsEmbedded(void* monitor) const {
return monitor && embedded_monitor_.load(std::memory_order_relaxed) == monitor;
}
private:
// This opaque handle is the entire state; no other data needs publishing.
std::atomic<void*> embedded_monitor_{nullptr};
};
inline CapturedCursorState& SharedCapturedCursorState() {
static CapturedCursorState state;
return state;
}
} // namespace crossdesk
#endif
@@ -0,0 +1,49 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-09-16
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _CURSOR_DRAW_H_
#define _CURSOR_DRAW_H_
#include <windows.h>
#include <cstdint>
namespace crossdesk {
// ptScreenPos is the input hotspot; DrawIconEx expects the bitmap's top-left.
// Return true only if a cursor intersects the captured rectangle and was drawn.
inline bool DrawCursorInCapture(HDC dc, const CURSORINFO& cursor, int left,
int top, int width, int height) {
if (!(cursor.flags & CURSOR_SHOWING) || !cursor.hCursor) return false;
struct CursorIconInfo : ICONINFO {
CursorIconInfo() : ICONINFO{} {}
~CursorIconInfo() {
if (hbmColor) DeleteObject(hbmColor);
if (hbmMask) DeleteObject(hbmMask);
}
} icon;
if (!GetIconInfo(cursor.hCursor, &icon)) return false;
BITMAP bitmap{};
if (!GetObject(icon.hbmColor ? icon.hbmColor : icon.hbmMask, sizeof(bitmap),
&bitmap))
return false;
const int cursor_width = bitmap.bmWidth;
const int cursor_height =
icon.hbmColor ? bitmap.bmHeight : bitmap.bmHeight / 2;
const int64_t x =
static_cast<int64_t>(cursor.ptScreenPos.x) - left - icon.xHotspot;
const int64_t y =
static_cast<int64_t>(cursor.ptScreenPos.y) - top - icon.yHotspot;
if (x >= width || y >= height || x + cursor_width <= 0 ||
y + cursor_height <= 0)
return false;
return DrawIconEx(dc, static_cast<int>(x), static_cast<int>(y),
cursor.hCursor, 0, 0, 0, nullptr, DI_NORMAL) != FALSE;
}
} // namespace crossdesk
#endif
@@ -6,6 +6,8 @@
#include <vector>
#include <display_stream_id.h>
#include "captured_cursor_state.h"
#include "cursor_draw.h"
#include "libyuv.h"
#include "rd_log.h"
@@ -184,18 +186,15 @@ void ScreenCapturerGdi::CaptureLoop() {
BitBlt(mem_dc, 0, 0, width, height, screen_dc, left, top,
SRCCOPY | CAPTUREBLT);
bool cursor_embedded = false;
if (show_cursor_) {
CURSORINFO ci{};
ci.cbSize = sizeof(CURSORINFO);
if (GetCursorInfo(&ci) && ci.flags == CURSOR_SHOWING && ci.hCursor) {
POINT pt = ci.ptScreenPos;
int cx = pt.x - left;
int cy = pt.y - top;
if (cx >= -64 && cy >= -64 && cx < width + 64 && cy < height + 64) {
DrawIconEx(mem_dc, cx, cy, ci.hCursor, 0, 0, 0, nullptr, DI_NORMAL);
}
if (GetCursorInfo(&ci)) {
cursor_embedded = DrawCursorInCapture(mem_dc, ci, left, top, width, height);
}
}
GdiFlush();
int stride_argb = width * 4;
int nv12_size = width * height * 3 / 2;
@@ -211,6 +210,7 @@ void ScreenCapturerGdi::CaptureLoop() {
width, height);
if (callback_) {
CapturedCursorFrameScope cursor_scope(cursor_embedded);
const std::string stream_id = MakeDisplayStreamId(idx);
callback_(nv12_frame_, nv12_size, width, height, stream_id.c_str(),
nullptr);
@@ -31,6 +31,9 @@ class ScreenCapturerGdi : public ScreenCapturer {
int Destroy() override;
int Start(bool show_cursor) override;
int Stop() override;
void SetCursorCapture(bool enabled) {
show_cursor_.store(enabled, std::memory_order_relaxed);
}
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
@@ -65,4 +68,4 @@ class ScreenCapturerGdi : public ScreenCapturer {
};
} // namespace crossdesk
#endif
#endif
@@ -8,6 +8,7 @@
#include <iostream>
#include <display_stream_id.h>
#include "captured_cursor_state.h"
#include "libyuv.h"
#include "rd_log.h"
@@ -72,6 +73,16 @@ HMONITOR GetPrimaryMonitor() {
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
int ScreenCapturerWgc::SetCursorCapture(bool enabled) {
int result = 0;
for (auto& session : sessions_) {
if (!session.inited_ || !session.running_) continue;
const int ret = session.session_->Start(enabled);
if (ret != 0) result = ret;
}
return result;
}
ScreenCapturerWgc::~ScreenCapturerWgc() {
Stop();
CleanUp();
@@ -409,6 +420,7 @@ void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
even_width, even_width, even_height);
const std::string stream_id = MakeDisplayStreamId(id);
CapturedCursorFrameScope cursor_scope(frame.cursor_captured);
on_data_(nv12_frame_, nv12_size, even_width, even_height,
stream_id.c_str(), nullptr);
}
@@ -27,6 +27,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
int Destroy() override;
int Start(bool show_cursor) override;
int Stop() override;
int SetCursorCapture(bool enabled);
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
@@ -16,6 +16,7 @@
#include <display_stream_id.h>
#include "captured_nv12_frame.h"
#include "captured_cursor_state.h"
#include "dxgi_cursor_state.h"
#include "interactive_state.h"
#include "named_pipe_deadline.h"
@@ -52,6 +53,8 @@ class WgcPluginCapturer final : public ScreenCapturer {
public:
using CreateFn = ScreenCapturer* (*)();
using DestroyFn = void (*)(ScreenCapturer*);
using SetCursorFn = decltype(&CrossDeskSetWgcCursorCapture);
using FrameCursorFn = decltype(&CrossDeskWgcFrameCapturesCursor);
static std::unique_ptr<ScreenCapturer> Create() {
std::filesystem::path plugin_path;
@@ -99,12 +102,38 @@ class WgcPluginCapturer final : public ScreenCapturer {
}
int Init(const int fps, cb_desktop_data cb) override {
return impl_ ? impl_->Init(fps, std::move(cb)) : -1;
if (!impl_) return -1;
return impl_->Init(fps, [this, cb = std::move(cb)](
unsigned char* data, int size, int width, int height,
const char* stream_id, const MiniRtcNativeVideoFrame* native_frame) {
bool embedded = false;
if (frame_cursor_fn_) {
embedded = frame_cursor_fn_();
} else if (legacy_capture_cursor_.load(std::memory_order_relaxed)) {
CURSORINFO info{};
info.cbSize = sizeof(info);
embedded = GetCursorInfo(&info) && (info.flags & CURSOR_SHOWING) != 0;
}
// The plugin has its own globals. Bridge only this callback's metadata
// into the executable before the wrapper accepts/publishes the frame.
CapturedCursorFrameScope cursor_scope(embedded);
cb(data, size, width, height, stream_id, native_frame);
});
}
int Destroy() override { return impl_ ? impl_->Destroy() : 0; }
int Start(bool show_cursor) override {
if (!frame_cursor_fn_) {
legacy_capture_cursor_.store(show_cursor, std::memory_order_relaxed);
}
return impl_ ? impl_->Start(show_cursor) : -1;
}
int SetCursorCapture(bool enabled) {
if (!impl_) return -1;
if (set_cursor_fn_) return set_cursor_fn_(impl_, enabled);
// Older plugins only apply the cursor setting at startup.
const int ret = impl_->Stop();
return ret == 0 ? Start(enabled) : ret;
}
int Stop() override { return impl_ ? impl_->Stop() : 0; }
int Pause(int monitor_index) override {
return impl_ ? impl_->Pause(monitor_index) : -1;
@@ -124,11 +153,21 @@ class WgcPluginCapturer final : public ScreenCapturer {
private:
WgcPluginCapturer(HMODULE module, ScreenCapturer* impl, DestroyFn destroy_fn)
: module_(module), impl_(impl), destroy_fn_(destroy_fn) {}
: module_(module), impl_(impl), destroy_fn_(destroy_fn) {
set_cursor_fn_ = reinterpret_cast<SetCursorFn>(
GetProcAddress(module, "CrossDeskSetWgcCursorCapture"));
frame_cursor_fn_ = reinterpret_cast<FrameCursorFn>(
GetProcAddress(module, "CrossDeskWgcFrameCapturesCursor"));
// Use the legacy restart path unless both parts of the extension exist.
if (!frame_cursor_fn_) set_cursor_fn_ = nullptr;
}
HMODULE module_ = nullptr;
ScreenCapturer* impl_ = nullptr;
DestroyFn destroy_fn_ = nullptr;
SetCursorFn set_cursor_fn_ = nullptr;
FrameCursorFn frame_cursor_fn_ = nullptr;
std::atomic<bool> legacy_capture_cursor_{false};
};
const char* CaptureBackendName(const ScreenCapturer* capturer) {
@@ -363,16 +402,19 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
const char* reported_stream_id,
const MiniRtcNativeVideoFrame* native_frame) {
if (size == ScreenCapturer::kBackendReset) {
SharedCapturedCursorState().Reset();
if (privacy_) privacy_->Fail("Capture restarted; privacy screen will turn off");
return;
}
if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) {
SharedDxgiCursorState().Reset();
SharedCapturedCursorState().Reset();
return;
}
const char* raw_stream_id = reported_stream_id ? reported_stream_id : "";
std::string mapped_stream_id;
void* frame_monitor = nullptr;
{
std::lock_guard<std::mutex> lock(alias_mutex_);
auto it = stream_id_alias_.find(raw_stream_id);
@@ -386,6 +428,14 @@ 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));
if (current_frame_has_cursor) {
for (size_t index = 0; index < canonical_displays_.size(); ++index) {
if (mapped_stream_id == MakeDisplayStreamId(index)) {
frame_monitor = canonical_displays_[index].handle;
break;
}
}
}
}
if (privacy_ && privacy_->Engaged() && !IsWindowsPrivacyDesktopAvailable()) {
privacy_->Fail("Secure or unavailable desktop; privacy screen will turn off");
@@ -400,6 +450,7 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
return;
}
invalid_stream_id_logged_.store(false, std::memory_order_relaxed);
SharedCapturedCursorState().Update(current_frame_has_cursor, frame_monitor);
if (post_secure_desktop_waiting_for_frame_.exchange(
false, std::memory_order_relaxed)) {
const ULONGLONG start_tick =
@@ -476,7 +527,10 @@ void ScreenCapturerWin::EmitCapturedFrame(
bool from_secure_desktop) {
// Helper frames do not use DXGI's pointer plane. Do not carry normal-desktop
// cursor suppression into the lock screen or UAC desktop.
if (from_secure_desktop) SharedDxgiCursorState().Reset();
if (from_secure_desktop) {
SharedDxgiCursorState().Reset();
SharedCapturedCursorState().Reset();
}
// Secure-desktop capture takes priority; remove privacy without dropping
// the helper frame or delaying the remote session.
if (from_secure_desktop && privacy_ && privacy_->Engaged()) {
@@ -543,9 +597,10 @@ bool ScreenCapturerWin::TryStartBackend(
return false;
}
const char* name = CaptureBackendName(candidate.get());
const bool show_cursor = show_cursor_.load(std::memory_order_relaxed);
int ret = candidate->Init(fps_, cb_);
if (ret == 0)
ret = candidate->Start(show_cursor_.load(std::memory_order_relaxed));
ret = candidate->Start(show_cursor);
if (ret != 0) {
LOG_WARN("Windows capturer: {} initialization/start failed (ret={})", name,
ret);
@@ -555,6 +610,7 @@ bool ScreenCapturerWin::TryStartBackend(
// Commit only after the replacement is running. Failed candidates release
// their resources without replacing the current backend.
impl_ = std::move(candidate);
applied_show_cursor_ = show_cursor;
RestoreMonitor(monitor_index);
LOG_INFO("Windows capturer: started {}", name);
return true;
@@ -568,6 +624,8 @@ int ScreenCapturerWin::Start(bool show_cursor) {
NotifyPrivacyCapture(false);
show_cursor_.store(show_cursor, std::memory_order_relaxed);
applied_show_cursor_ = show_cursor;
SharedCapturedCursorState().Reset();
paused_.store(false, std::memory_order_relaxed);
invalid_stream_id_logged_.store(false, std::memory_order_relaxed);
@@ -636,10 +694,12 @@ int ScreenCapturerWin::Stop() {
post_secure_desktop_drop_logged_.store(false, std::memory_order_relaxed);
post_secure_desktop_started_tick_.store(0, std::memory_order_relaxed);
int ret = 0;
// Drain management operations before stopping/replacing their backend.
StopSecureCaptureThread();
if (impl_) {
ret = impl_->Stop();
}
StopSecureCaptureThread();
SharedCapturedCursorState().Reset();
SharedSecureDesktopCursorState().SetActive(false);
StopSecureDesktopSharedCapture(secure_shared_session_id_);
return ret;
@@ -798,6 +858,7 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
impl_->Stop();
int ret = impl_->Start(show_cursor);
if (ret == 0) {
applied_show_cursor_ = show_cursor;
RestoreMonitor(current_monitor);
return true;
}
@@ -812,6 +873,7 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
ret = impl_->Start(show_cursor);
}
if (ret == 0) {
applied_show_cursor_ = show_cursor;
RestoreMonitor(current_monitor);
return true;
}
@@ -834,6 +896,24 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
return false;
}
void ScreenCapturerWin::ApplyCursorCaptureSetting() {
const bool requested = show_cursor_.load(std::memory_order_relaxed);
if (!impl_ || requested == applied_show_cursor_) return;
int ret = 0;
if (auto* gdi = dynamic_cast<ScreenCapturerGdi*>(impl_.get())) {
gdi->SetCursorCapture(requested);
} else if (auto* wgc = dynamic_cast<WgcPluginCapturer*>(impl_.get())) {
ret = wgc->SetCursorCapture(requested);
}
if (ret == 0) {
applied_show_cursor_ = requested;
LOG_INFO("Windows capturer cursor capture updated: backend={}, enabled={}",
CaptureBackendName(impl_.get()), requested);
} else {
LOG_WARN("Windows capturer cursor update failed, ret={}; will retry", ret);
}
}
bool ScreenCapturerWin::GetCurrentCaptureRegion(int* left, int* top, int* width,
int* height,
std::string* display_name) {
@@ -1113,6 +1193,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
bool post_secure_restart_pending = false;
ULONGLONG post_secure_restart_deadline_tick = 0;
ULONGLONG last_post_secure_restart_tick = 0;
ULONGLONG next_cursor_update_tick = 0;
SecureDesktopServiceStatus status;
SecureDesktopStatusPoller status_poller(
QuerySecureDesktopServiceStatus,
@@ -1250,6 +1331,10 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
!RestartCaptureBackendAfterSecureDesktop();
}
}
if (!post_secure_restart_pending && now >= next_cursor_update_tick) {
next_cursor_update_tick = now + 500;
ApplyCursorCaptureSetting();
}
std::this_thread::sleep_for(
std::chrono::milliseconds(status.service_available ? 50 : 200));
continue;
@@ -37,6 +37,10 @@ class ScreenCapturerWin : public ScreenCapturer {
int Destroy() override;
int Start(bool show_cursor) override;
int Stop() override;
// Applied on the capture-management thread, including secure-desktop capture.
void SetCursorCapture(bool enabled) {
show_cursor_.store(enabled, std::memory_order_relaxed);
}
int Pause(int monitor_index) override;
int Resume(int monitor_index) override;
@@ -75,6 +79,7 @@ class ScreenCapturerWin : public ScreenCapturer {
std::atomic<bool> running_{false};
std::atomic<bool> paused_{false};
std::atomic<bool> show_cursor_{true};
bool applied_show_cursor_ = true;
std::atomic<int> monitor_index_{0};
int initial_monitor_index_ = 0;
std::atomic<bool> secure_desktop_capture_active_{false};
@@ -109,6 +114,7 @@ class ScreenCapturerWin : public ScreenCapturer {
bool from_secure_desktop = false);
void StopSecureCaptureThread();
bool RestartCaptureBackendAfterSecureDesktop();
void ApplyCursorCaptureSetting();
void SecureDesktopCaptureLoop();
bool GetCurrentCaptureRegion(int* left, int* top, int* width, int* height,
std::string* display_name);
@@ -24,6 +24,10 @@ CROSSDESK_WGC_PLUGIN_API crossdesk::ScreenCapturer*
CrossDeskCreateWgcCapturer();
CROSSDESK_WGC_PLUGIN_API void CrossDeskDestroyWgcCapturer(
crossdesk::ScreenCapturer* capturer);
// Optional extensions; they do not change ScreenCapturer's virtual ABI.
CROSSDESK_WGC_PLUGIN_API int CrossDeskSetWgcCursorCapture(
crossdesk::ScreenCapturer* capturer, bool enabled);
CROSSDESK_WGC_PLUGIN_API bool CrossDeskWgcFrameCapturesCursor();
}
#endif
#endif
@@ -1,5 +1,6 @@
#include <mutex>
#include "captured_cursor_state.h"
#include "path_manager.h"
#include "rd_log.h"
#include "screen_capturer_wgc.h"
@@ -27,4 +28,12 @@ crossdesk::ScreenCapturer* CrossDeskCreateWgcCapturer() {
void CrossDeskDestroyWgcCapturer(crossdesk::ScreenCapturer* capturer) {
delete capturer;
}
int CrossDeskSetWgcCursorCapture(crossdesk::ScreenCapturer* capturer, bool enabled) {
return static_cast<crossdesk::ScreenCapturerWgc*>(capturer)->SetCursorCapture(enabled);
}
bool CrossDeskWgcFrameCapturesCursor() {
return crossdesk::current_frame_has_cursor;
}
}
@@ -13,6 +13,7 @@ class WgcSession {
unsigned int row_pitch;
const unsigned char* data;
bool cursor_captured;
};
class wgc_session_observer {
@@ -38,4 +39,4 @@ class WgcSession {
virtual ~WgcSession() {};
};
} // namespace crossdesk
#endif
#endif
@@ -201,7 +201,13 @@ int WgcSessionImpl::StartCaptureLocked(bool show_cursor) {
}
int WgcSessionImpl::StartLocked(bool show_cursor) {
if (is_running_) return 0;
const bool was_paused = is_paused_;
if (is_running_) {
if (last_show_cursor_ == show_cursor) return 0;
// Replace the pool along with the setting, so buffered frames from the
// previous cursor mode cannot be labeled with the new mode.
StopLocked();
}
last_show_cursor_ = show_cursor;
if (!is_initialized_) {
@@ -218,6 +224,7 @@ int WgcSessionImpl::StartLocked(bool show_cursor) {
LOG_WARN("WGC: start capture failed, rebuilding capture item");
CleanUpLocked();
is_paused_ = was_paused;
ret = InitializeLocked();
if (ret != 0) {
return ret;
@@ -259,10 +266,12 @@ void WgcSessionImpl::OnFrame(
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
std::lock_guard locker(lock_);
if (!is_running_ || sender != capture_framepool_) return;
auto is_new_size = false;
try {
auto frame = sender.TryGetNextFrame();
if (!frame) return;
auto frame_size = frame.ContentSize();
if (frame_size.Width != capture_frame_size_.Width ||
@@ -310,12 +319,21 @@ void WgcSessionImpl::OnFrame(
// copy data from map_result.pData
if (map_result.pData && observer_) {
CURSORINFO cursor{};
cursor.cbSize = sizeof(cursor);
const bool cursor_captured =
last_show_cursor_ && GetCursorInfo(&cursor) && cursor.hCursor &&
(cursor.flags & CURSOR_SHOWING) != 0 &&
(target_.is_window ||
MonitorFromPoint(cursor.ptScreenPos, MONITOR_DEFAULTTONULL) ==
target_.hmonitor);
observer_->OnFrame(
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
static_cast<unsigned int>(frame_size.Height),
map_result.RowPitch,
const_cast<const unsigned char*>(
(unsigned char*)map_result.pData)},
(unsigned char*)map_result.pData),
cursor_captured},
id_);
}
@@ -26,6 +26,7 @@
#include "rd_log.h"
#include "session_helper_shared.h"
#include "windows_cursor_state.h"
#include "cursor_draw.h"
#include "windows_input_marker.h"
namespace {
@@ -1201,21 +1202,12 @@ struct SecureDesktopGdiResources {
cursor_snapshot.hidden_reason = static_cast<uint32_t>(state.hidden_reason);
cursor_snapshot.x = cursor.ptScreenPos.x;
cursor_snapshot.y = cursor.ptScreenPos.y;
if (request.show_cursor && cursor_snapshot.visible &&
cursor.hCursor != nullptr) {
const int x = cursor.ptScreenPos.x - request.left;
const int y = cursor.ptScreenPos.y - request.top;
if (x >= -64 && y >= -64 && x < request.width + 64 &&
y < request.height + 64) {
if (DrawIconEx(mem_dc, x, y, cursor.hCursor, 0, 0, 0, nullptr,
DI_NORMAL)) {
// A frame with an embedded cursor must not get a second cursor
// overlay from a native controller sharing the web capture path.
cursor_snapshot.visible = 0;
cursor_snapshot.render_mode =
static_cast<uint32_t>(crossdesk::CursorRenderMode::embedded);
}
}
if (request.show_cursor && crossdesk::DrawCursorInCapture(
mem_dc, cursor, request.left, request.top,
request.width, request.height)) {
cursor_snapshot.visible = 0;
cursor_snapshot.render_mode =
static_cast<uint32_t>(crossdesk::CursorRenderMode::embedded);
}
}
// Complete GDI writes before libyuv reads the DIB's pixel memory.