[fix] restore remote cursor visibility during Windows privacy screen

This commit is contained in:
dijunkun
2026-09-16 20:27:59 +08:00
parent ad62e60b9a
commit 1d9242515a
2 changed files with 31 additions and 7 deletions
@@ -9,6 +9,7 @@
#include <filesystem>
#include <vector>
#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;
}
@@ -1,8 +1,8 @@
#ifndef CROSSDESK_DXGI_CURSOR_STATE_H_
#define CROSSDESK_DXGI_CURSOR_STATE_H_
#include <atomic>
#include <cstdint>
#include <mutex>
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<std::mutex> 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<std::mutex> lock(mutex_);
embedded_monitor_ = nullptr;
}
void SetPrivacyCursorHidden(bool hidden) {
std::lock_guard<std::mutex> 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<std::mutex> 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<void*> embedded_monitor_{nullptr};
mutable std::mutex mutex_;
void* embedded_monitor_ = nullptr;
bool privacy_cursor_hidden_ = false;
};
inline DxgiCursorState& SharedDxgiCursorState() {