From 57bb91dba3a58e6f95f7e380ac5f386e0a7fedf7 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 16 Sep 2026 13:51:35 +0800 Subject: [PATCH] [fix] prevent duplicate cursors during Windows window dragging --- .../src/gui/runtime/cursor_state_provider.cpp | 11 +++-- .../screen_capturer/dxgi_cursor_state.h | 48 +++++++++++++++++++ .../screen_capturer/screen_capturer_dxgi.cpp | 12 +++++ .../screen_capturer/screen_capturer_win.cpp | 5 ++ 4 files changed, 73 insertions(+), 3 deletions(-) create mode 100644 apps/desktop/src/platform/windows/screen_capturer/dxgi_cursor_state.h diff --git a/apps/desktop/src/gui/runtime/cursor_state_provider.cpp b/apps/desktop/src/gui/runtime/cursor_state_provider.cpp index e158f487..a5cd3bf8 100644 --- a/apps/desktop/src/gui/runtime/cursor_state_provider.cpp +++ b/apps/desktop/src/gui/runtime/cursor_state_provider.cpp @@ -6,6 +6,7 @@ #include +#include "platform/windows/screen_capturer/dxgi_cursor_state.h" #include "runtime/cursor_position.h" namespace crossdesk { @@ -52,11 +53,15 @@ bool CursorStateProvider::Sample(const std::vector& displays, if (!GetCursorInfo(&info)) return false; state->seq = 0; - state->visible = (info.flags & CURSOR_SHOWING) != 0; - state->shape = state->visible ? ShapeFromWindowsCursor(info.hCursor) - : RemoteCursorShape::none; NormalizeCursorPosition(info.ptScreenPos.x, info.ptScreenPos.y, displays, preferred_display, state); + void* cursor_monitor = state->position_valid + ? displays[state->display_id].handle + : nullptr; + state->visible = SharedDxgiCursorState().ShouldDrawCursor( + (info.flags & CURSOR_SHOWING) != 0, cursor_monitor); + state->shape = state->visible ? ShapeFromWindowsCursor(info.hCursor) + : RemoteCursorShape::none; return true; } diff --git a/apps/desktop/src/platform/windows/screen_capturer/dxgi_cursor_state.h b/apps/desktop/src/platform/windows/screen_capturer/dxgi_cursor_state.h new file mode 100644 index 00000000..ad88b0ea --- /dev/null +++ b/apps/desktop/src/platform/windows/screen_capturer/dxgi_cursor_state.h @@ -0,0 +1,48 @@ +#ifndef CROSSDESK_DXGI_CURSOR_STATE_H_ +#define CROSSDESK_DXGI_CURSOR_STATE_H_ + +#include +#include + +namespace crossdesk { + +// DXGI's Visible flag describes the separate hardware pointer, not the +// system cursor. When it is false, a visible system cursor can already be +// included in the desktop texture (for example, during window dragging). +// Share that distinction with the GUI thread without changing cursor input +// coordinates or the wire protocol. +class DxgiCursorState { + public: + void Update(int64_t last_mouse_update_time, bool separate_pointer_visible, + void* monitor) { + // PointerPosition is undefined on desktop-only updates. Retain the last + // pointer state until DXGI actually reports another pointer update. + if (last_mouse_update_time == 0) return; + embedded_monitor_.store(separate_pointer_visible ? nullptr : monitor, + std::memory_order_relaxed); + } + + void Reset() { + embedded_monitor_.store(nullptr, std::memory_order_relaxed); + } + + bool ShouldDrawCursor(bool system_cursor_visible, void* cursor_monitor) const { + // A hidden cursor or a pointer on another monitor also makes DXGI report + // Visible=false. Only suppress a visible cursor on the captured monitor. + return system_cursor_visible && + (!cursor_monitor || + embedded_monitor_.load(std::memory_order_relaxed) != cursor_monitor); + } + + private: + std::atomic embedded_monitor_{nullptr}; +}; + +inline DxgiCursorState& SharedDxgiCursorState() { + static DxgiCursorState state; + return state; +} + +} // namespace crossdesk + +#endif // CROSSDESK_DXGI_CURSOR_STATE_H_ diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp index f31eea30..1c4cdb24 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_dxgi.cpp @@ -7,6 +7,7 @@ #include #include +#include "dxgi_cursor_state.h" #include "libyuv.h" #include "rd_log.h" @@ -232,6 +233,7 @@ void ScreenCapturerDxgi::EnumerateDisplays() { } bool ScreenCapturerDxgi::CreateDuplicationForMonitor(int monitor_index) { + SharedDxgiCursorState().Reset(); if (monitor_index < 0 || monitor_index >= (int)outputs_.size()) return false; Microsoft::WRL::ComPtr output1; HRESULT hr = outputs_[monitor_index]->QueryInterface( @@ -284,6 +286,7 @@ bool ScreenCapturerDxgi::RecreateDuplicationForCurrentMonitor() { } void ScreenCapturerDxgi::ReleaseDuplication() { + SharedDxgiCursorState().Reset(); ++duplication_generation_; staging_.Reset(); if (duplication_) { @@ -344,6 +347,7 @@ void ScreenCapturerDxgi::CaptureLoop() { // a backend rebuild. Normal error/reset handling releases optional privacy. cached_frame_valid = false; if (FAILED(hr)) { + SharedDxgiCursorState().Reset(); LOG_ERROR("DXGI: AcquireNextFrame failed, hr={}", (int)hr); capture_lock.unlock(); if (callback_) @@ -352,6 +356,14 @@ void ScreenCapturerDxgi::CaptureLoop() { continue; } + if (frame_monitor >= 0 && + frame_monitor < static_cast(display_info_list_.size())) { + SharedDxgiCursorState().Update( + frame_info.LastMouseUpdateTime.QuadPart, + frame_info.PointerPosition.Visible != FALSE, + display_info_list_[frame_monitor].handle); + } + Microsoft::WRL::ComPtr acquired_tex; if (desktop_resource) { hr = desktop_resource->QueryInterface( diff --git a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp index 80fd2a77..9eb60b25 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp +++ b/apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp @@ -16,6 +16,7 @@ #include #include "captured_nv12_frame.h" +#include "dxgi_cursor_state.h" #include "interactive_state.h" #include "named_pipe_deadline.h" #include "rd_log.h" @@ -364,6 +365,7 @@ int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) { return; } if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) { + SharedDxgiCursorState().Reset(); return; } @@ -470,6 +472,9 @@ void ScreenCapturerWin::EmitCapturedFrame( unsigned char* data, int size, int width, int height, const char* stream_id, const MiniRtcNativeVideoFrame* native_frame, 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(); // Secure-desktop capture takes priority; remove privacy without dropping // the helper frame or delaying the remote session. if (from_secure_desktop && privacy_ && privacy_->Engaged()) {