diff --git a/apps/desktop/src/platform/windows/privacy/privacy_cursor_guard.cpp b/apps/desktop/src/platform/windows/privacy/privacy_cursor_guard.cpp index feb387c0..4eecdd80 100644 --- a/apps/desktop/src/platform/windows/privacy/privacy_cursor_guard.cpp +++ b/apps/desktop/src/platform/windows/privacy/privacy_cursor_guard.cpp @@ -9,6 +9,7 @@ #include #include +#include "../screen_capturer/dxgi_cursor_state.h" #include "privacy_cursor_api.h" #include "rd_log.h" @@ -105,6 +106,9 @@ bool PrivacyCursorGuard::Start(std::string& error) { process_ = child.hProcess; stop_event_ = stop.value; stop.value = nullptr; + // The helper may hide the pointer before signaling readiness. Track its + // entire lifetime, including startup failure and delayed restoration. + SharedDxgiCursorState().SetPrivacyCursorHidden(true); HANDLE startup_events[] = {ready.value, process_}; const DWORD result = WaitForMultipleObjects(2, startup_events, FALSE, 2000); const DWORD ready_error = @@ -127,6 +131,7 @@ bool PrivacyCursorGuard::RestoreAfterFailure(std::string& error) { return Failed("Restore system cursor after helper failure", error); } restore_pending_ = false; + SharedDxgiCursorState().SetPrivacyCursorHidden(false); return true; } @@ -152,6 +157,7 @@ bool PrivacyCursorGuard::Stop(std::string& error) { CloseHandle(stop_event_); stop_event_ = nullptr; if (code != ERROR_SUCCESS) return RestoreAfterFailure(error); + SharedDxgiCursorState().SetPrivacyCursorHidden(false); 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 index ad88b0ea..71bfa501 100644 --- a/apps/desktop/src/platform/windows/screen_capturer/dxgi_cursor_state.h +++ b/apps/desktop/src/platform/windows/screen_capturer/dxgi_cursor_state.h @@ -1,8 +1,8 @@ #ifndef CROSSDESK_DXGI_CURSOR_STATE_H_ #define CROSSDESK_DXGI_CURSOR_STATE_H_ -#include #include +#include namespace crossdesk { @@ -18,24 +18,42 @@ class DxgiCursorState { // 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); + std::lock_guard lock(mutex_); + if (privacy_cursor_hidden_) return; + embedded_monitor_ = separate_pointer_visible ? nullptr : monitor; } void Reset() { - embedded_monitor_.store(nullptr, std::memory_order_relaxed); + std::lock_guard lock(mutex_); + embedded_monitor_ = nullptr; + } + + void SetPrivacyCursorHidden(bool hidden) { + std::lock_guard lock(mutex_); + if (privacy_cursor_hidden_ == hidden) return; + privacy_cursor_hidden_ = hidden; + // Pointer updates from before/during suppression cannot describe whether + // the restored cursor is embedded. Wait for a fresh DXGI pointer update. + embedded_monitor_ = nullptr; } bool ShouldDrawCursor(bool system_cursor_visible, void* cursor_monitor) const { + std::lock_guard lock(mutex_); + // MagShowSystemCursor(FALSE) hides the local pointer plane without changing + // CURSOR_SHOWING. DXGI Visible=false then means hidden, not embedded in the + // captured frame. Keep sending the application's cursor to the controller. + // CURSOR_SHOWING still takes precedence when the application hides it. + if (privacy_cursor_hidden_) return system_cursor_visible; // 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); + (!cursor_monitor || embedded_monitor_ != cursor_monitor); } private: - std::atomic embedded_monitor_{nullptr}; + mutable std::mutex mutex_; + void* embedded_monitor_ = nullptr; + bool privacy_cursor_hidden_ = false; }; inline DxgiCursorState& SharedDxgiCursorState() {