From c4aca303c8c84c618fe401d7908fa401650e4c9c Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 16 Sep 2026 14:26:13 +0800 Subject: [PATCH] [fix] restore cursor visibility on Windows lock screen --- .../src/gui/runtime/cursor_state_provider.cpp | 43 +++++--------- .../windows/input/windows_cursor_shape.h | 32 +++++++++++ .../screen_capturer/screen_capturer_win.cpp | 14 ++++- .../secure_desktop_cursor_state.h | 56 +++++++++++++++++++ .../windows/service/session_helper_main.cpp | 28 ++++++++-- .../windows/service/session_helper_shared.h | 8 ++- apps/desktop/xmake/targets.lua | 10 +++- 7 files changed, 152 insertions(+), 39 deletions(-) create mode 100644 apps/desktop/src/platform/windows/input/windows_cursor_shape.h create mode 100644 apps/desktop/src/platform/windows/screen_capturer/secure_desktop_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 a5cd3bf8..e2508acb 100644 --- a/apps/desktop/src/gui/runtime/cursor_state_provider.cpp +++ b/apps/desktop/src/gui/runtime/cursor_state_provider.cpp @@ -6,38 +6,12 @@ #include +#include "platform/windows/input/windows_cursor_shape.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" namespace crossdesk { -namespace { - -bool IsSystemCursor(HCURSOR cursor, LPCWSTR resource) { - return cursor != nullptr && cursor == LoadCursorW(nullptr, resource); -} - -RemoteCursorShape ShapeFromWindowsCursor(HCURSOR cursor) { - if (IsSystemCursor(cursor, IDC_HELP)) return RemoteCursorShape::help; - if (IsSystemCursor(cursor, IDC_HAND)) return RemoteCursorShape::pointer; - if (IsSystemCursor(cursor, IDC_APPSTARTING)) - return RemoteCursorShape::progress; - if (IsSystemCursor(cursor, IDC_WAIT)) return RemoteCursorShape::wait; - if (IsSystemCursor(cursor, IDC_CROSS)) return RemoteCursorShape::crosshair; - if (IsSystemCursor(cursor, IDC_IBEAM)) return RemoteCursorShape::text; - if (IsSystemCursor(cursor, IDC_NO)) - return RemoteCursorShape::not_allowed; - if (IsSystemCursor(cursor, IDC_SIZEALL)) return RemoteCursorShape::move; - if (IsSystemCursor(cursor, IDC_SIZEWE)) return RemoteCursorShape::ew_resize; - if (IsSystemCursor(cursor, IDC_SIZENS)) return RemoteCursorShape::ns_resize; - if (IsSystemCursor(cursor, IDC_SIZENESW)) - return RemoteCursorShape::nesw_resize; - if (IsSystemCursor(cursor, IDC_SIZENWSE)) - return RemoteCursorShape::nwse_resize; - if (IsSystemCursor(cursor, IDC_UPARROW)) return RemoteCursorShape::n_resize; - return RemoteCursorShape::default_cursor; -} - -} // namespace struct CursorStateProvider::Impl {}; @@ -48,6 +22,19 @@ bool CursorStateProvider::Sample(const std::vector& displays, int preferred_display, CursorState* state) { if (!state) return false; + SecureDesktopCursorSnapshot secure_cursor{}; + if (SharedSecureDesktopCursorState().Get(&secure_cursor)) { + if (!secure_cursor.valid) return false; + state->seq = 0; + NormalizeCursorPosition(secure_cursor.x, secure_cursor.y, displays, + preferred_display, state); + state->visible = secure_cursor.visible != 0; + state->shape = state->visible + ? static_cast(secure_cursor.shape) + : RemoteCursorShape::none; + return true; + } + CURSORINFO info{}; info.cbSize = sizeof(info); if (!GetCursorInfo(&info)) return false; diff --git a/apps/desktop/src/platform/windows/input/windows_cursor_shape.h b/apps/desktop/src/platform/windows/input/windows_cursor_shape.h new file mode 100644 index 00000000..1c887a9a --- /dev/null +++ b/apps/desktop/src/platform/windows/input/windows_cursor_shape.h @@ -0,0 +1,32 @@ +#ifndef CROSSDESK_WINDOWS_CURSOR_SHAPE_H_ +#define CROSSDESK_WINDOWS_CURSOR_SHAPE_H_ + +#include + +#include + +namespace crossdesk { + +inline RemoteCursorShape ShapeFromWindowsCursor(HCURSOR cursor) { + const auto is_system_cursor = [cursor](LPCWSTR resource) { + return cursor != nullptr && cursor == LoadCursorW(nullptr, resource); + }; + if (is_system_cursor(IDC_HELP)) return RemoteCursorShape::help; + if (is_system_cursor(IDC_HAND)) return RemoteCursorShape::pointer; + if (is_system_cursor(IDC_APPSTARTING)) return RemoteCursorShape::progress; + if (is_system_cursor(IDC_WAIT)) return RemoteCursorShape::wait; + if (is_system_cursor(IDC_CROSS)) return RemoteCursorShape::crosshair; + if (is_system_cursor(IDC_IBEAM)) return RemoteCursorShape::text; + if (is_system_cursor(IDC_NO)) return RemoteCursorShape::not_allowed; + if (is_system_cursor(IDC_SIZEALL)) return RemoteCursorShape::move; + if (is_system_cursor(IDC_SIZEWE)) return RemoteCursorShape::ew_resize; + if (is_system_cursor(IDC_SIZENS)) return RemoteCursorShape::ns_resize; + if (is_system_cursor(IDC_SIZENESW)) return RemoteCursorShape::nesw_resize; + if (is_system_cursor(IDC_SIZENWSE)) return RemoteCursorShape::nwse_resize; + if (is_system_cursor(IDC_UPARROW)) return RemoteCursorShape::n_resize; + return RemoteCursorShape::default_cursor; +} + +} // namespace crossdesk + +#endif // CROSSDESK_WINDOWS_CURSOR_SHAPE_H_ 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 9eb60b25..3b4c9523 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 @@ -22,6 +22,7 @@ #include "rd_log.h" #include "screen_capturer_dxgi.h" #include "screen_capturer_gdi.h" +#include "secure_desktop_cursor_state.h" #include "secure_desktop_frame_schedule.h" #include "secure_desktop_status_poller.h" #include "service_host.h" @@ -225,6 +226,7 @@ bool ParseSecureDesktopFrameResponse(const std::vector& response, *width_out = static_cast(header.width); *height_out = static_cast(header.height); nv12_frame_out->assign(response.begin() + sizeof(header), response.end()); + SharedSecureDesktopCursorState().Update(header.cursor); return true; } @@ -612,6 +614,7 @@ int ScreenCapturerWin::Start(bool show_cursor) { running_.store(true, std::memory_order_relaxed); NotifyPrivacyCapture(true); + SharedSecureDesktopCursorState().SetActive(false); secure_desktop_capture_active_.store(false, std::memory_order_relaxed); post_secure_desktop_waiting_for_frame_.store(false, std::memory_order_relaxed); @@ -637,6 +640,7 @@ int ScreenCapturerWin::Stop() { ret = impl_->Stop(); } StopSecureCaptureThread(); + SharedSecureDesktopCursorState().SetActive(false); StopSecureDesktopSharedCapture(secure_shared_session_id_); return ret; } @@ -1018,6 +1022,9 @@ bool ScreenCapturerWin::ReadSecureDesktopSharedFrame( } nv12_frame_out->resize(payload_size); + const SecureDesktopCursorSnapshot cursor = header->cursor; + const int captured_width = static_cast(header->width); + const int captured_height = static_cast(header->height); std::memcpy(nv12_frame_out->data(), secure_frame_view_ + sizeof(*header), payload_size); MemoryBarrier(); @@ -1028,8 +1035,9 @@ bool ScreenCapturerWin::ReadSecureDesktopSharedFrame( return false; } - *width_out = static_cast(header->width); - *height_out = static_cast(header->height); + *width_out = captured_width; + *height_out = captured_height; + SharedSecureDesktopCursorState().Update(cursor); return true; } @@ -1172,6 +1180,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { secure_desktop_capture_active_.store(status.capture_active, std::memory_order_relaxed); + SharedSecureDesktopCursorState().SetActive(status.capture_active); if (status.capture_active != previous.capture_active || status.interactive_stage != previous.interactive_stage || status.active_session_id != previous.active_session_id || @@ -1358,6 +1367,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { report_stats(true); StopSecureDesktopSharedCapture(secure_shared_session_id_); secure_desktop_capture_active_.store(false, std::memory_order_relaxed); + SharedSecureDesktopCursorState().SetActive(false); } } // namespace crossdesk diff --git a/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_cursor_state.h b/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_cursor_state.h new file mode 100644 index 00000000..0cd6b9fb --- /dev/null +++ b/apps/desktop/src/platform/windows/screen_capturer/secure_desktop_cursor_state.h @@ -0,0 +1,56 @@ +#ifndef CROSSDESK_SECURE_DESKTOP_CURSOR_STATE_H_ +#define CROSSDESK_SECURE_DESKTOP_CURSOR_STATE_H_ + +#include +#include + +namespace crossdesk { + +// Fixed-width IPC metadata. Cursor handles belong to the helper's desktop and +// must be converted to a semantic shape before crossing the process boundary. +struct SecureDesktopCursorSnapshot { + uint32_t valid = 0; + uint32_t visible = 0; + uint32_t shape = 0; + int32_t x = 0; + int32_t y = 0; +}; +static_assert(sizeof(SecureDesktopCursorSnapshot) == 20); + +// The capture thread publishes helper samples for the GUI thread. While secure +// capture is active, even an unavailable sample must not fall back to sampling +// the ordinary desktop, which can report a hidden or stale cursor. +class SecureDesktopCursorState { + public: + void SetActive(bool active) { + std::lock_guard lock(mutex_); + if (active_ != active) snapshot_ = {}; + active_ = active; + } + + void Update(const SecureDesktopCursorSnapshot& snapshot) { + std::lock_guard lock(mutex_); + if (active_ && snapshot.valid) snapshot_ = snapshot; + } + + bool Get(SecureDesktopCursorSnapshot* snapshot) const { + std::lock_guard lock(mutex_); + if (!active_ || !snapshot) return false; + *snapshot = snapshot_; + return true; + } + + private: + mutable std::mutex mutex_; + bool active_ = false; + SecureDesktopCursorSnapshot snapshot_{}; +}; + +inline SecureDesktopCursorState& SharedSecureDesktopCursorState() { + static SecureDesktopCursorState state; + return state; +} + +} // namespace crossdesk + +#endif // CROSSDESK_SECURE_DESKTOP_CURSOR_STATE_H_ diff --git a/apps/desktop/src/platform/windows/service/session_helper_main.cpp b/apps/desktop/src/platform/windows/service/session_helper_main.cpp index d3fdc34d..1e8bdfc4 100644 --- a/apps/desktop/src/platform/windows/service/session_helper_main.cpp +++ b/apps/desktop/src/platform/windows/service/session_helper_main.cpp @@ -25,6 +25,7 @@ #include "path_manager.h" #include "rd_log.h" #include "session_helper_shared.h" +#include "windows_cursor_shape.h" #include "windows_input_marker.h" namespace { @@ -1135,6 +1136,7 @@ struct SecureDesktopGdiResources { void* bits = nullptr; const char* error = nullptr; DWORD error_code = ERROR_SUCCESS; + crossdesk::SecureDesktopCursorSnapshot cursor_snapshot{}; ~SecureDesktopGdiResources() { if (old_bitmap != nullptr && old_bitmap != HGDI_ERROR) { @@ -1183,16 +1185,30 @@ struct SecureDesktopGdiResources { request.left, request.top, SRCCOPY | CAPTUREBLT)) { return Fail("bitblt_failed"); } - if (request.show_cursor) { - CURSORINFO cursor{}; - cursor.cbSize = sizeof(cursor); - if (GetCursorInfo(&cursor) && cursor.flags == CURSOR_SHOWING && + // Sample on the thread bound to the captured input desktop, including when + // native controllers render the cursor themselves (show_cursor == false). + cursor_snapshot = {}; + CURSORINFO cursor{}; + cursor.cbSize = sizeof(cursor); + if (GetCursorInfo(&cursor)) { + cursor_snapshot.valid = 1; + cursor_snapshot.visible = (cursor.flags & CURSOR_SHOWING) != 0; + cursor_snapshot.shape = static_cast( + crossdesk::ShapeFromWindowsCursor(cursor.hCursor)); + 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) { - DrawIconEx(mem_dc, x, y, cursor.hCursor, 0, 0, 0, nullptr, DI_NORMAL); + 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; + } } } } @@ -1237,6 +1253,7 @@ std::vector CaptureSecureDesktopFrame( header.height = static_cast(request.height); header.payload_size = static_cast(capture_buffers->nv12_frame.size()); + header.cursor = capture.cursor_snapshot; std::vector response(sizeof(header) + capture_buffers->nv12_frame.size()); @@ -1362,6 +1379,7 @@ void SecureDesktopSharedCaptureThread(SecureSharedCaptureState* capture_state) { header->width = static_cast(request.width); header->height = static_cast(request.height); header->payload_size = static_cast(nv12_frame.size()); + header->cursor = capture->cursor_snapshot; std::memcpy(payload, nv12_frame.data(), nv12_frame.size()); header->sequence = ++capture_state->sequence; MemoryBarrier(); diff --git a/apps/desktop/src/platform/windows/service/session_helper_shared.h b/apps/desktop/src/platform/windows/service/session_helper_shared.h index 09b6659e..c150b8f2 100644 --- a/apps/desktop/src/platform/windows/service/session_helper_shared.h +++ b/apps/desktop/src/platform/windows/service/session_helper_shared.h @@ -12,6 +12,8 @@ #include #include +#include "secure_desktop_cursor_state.h" + namespace crossdesk { inline constexpr wchar_t kCrossDeskSessionHelperPipePrefix[] = @@ -33,7 +35,9 @@ inline constexpr wchar_t kCrossDeskSecureDesktopFrameMappingPrefix[] = inline constexpr wchar_t kCrossDeskSecureDesktopFrameReadyEventPrefix[] = L"Global\\CrossDeskSecureDesktopFrameReady-"; inline constexpr uint32_t kCrossDeskSecureDesktopFrameMagic = 0x50444358; -inline constexpr uint32_t kCrossDeskSecureDesktopFrameVersion = 1; +// Version 2 adds cursor metadata to both headers. The GUI and local session +// helper must be deployed together; the remote-control wire format is unchanged. +inline constexpr uint32_t kCrossDeskSecureDesktopFrameVersion = 2; #pragma pack(push, 1) struct CrossDeskSecureDesktopFrameHeader { @@ -44,6 +48,7 @@ struct CrossDeskSecureDesktopFrameHeader { uint32_t width; uint32_t height; uint32_t payload_size; + SecureDesktopCursorSnapshot cursor; }; struct CrossDeskSecureDesktopSharedFrameHeader { @@ -57,6 +62,7 @@ struct CrossDeskSecureDesktopSharedFrameHeader { uint32_t height; uint32_t payload_size; uint32_t buffer_size; + SecureDesktopCursorSnapshot cursor; }; #pragma pack(pop) diff --git a/apps/desktop/xmake/targets.lua b/apps/desktop/xmake/targets.lua index ad95fbf0..994ab51d 100644 --- a/apps/desktop/xmake/targets.lua +++ b/apps/desktop/xmake/targets.lua @@ -459,7 +459,8 @@ function setup_targets() "apps/desktop/src/platform/windows/service/service_host.cpp") add_files("apps/desktop/resources/windows/crossdesk_service.rc", {defines = windows_resource_defines}) - add_includedirs("apps/desktop/src/platform/windows/service") + add_includedirs("apps/desktop/src/platform/windows/service", + "apps/desktop/src/platform/windows/screen_capturer") target("crossdesk_session_helper") set_kind("binary") @@ -470,8 +471,10 @@ function setup_targets() add_files("apps/desktop/resources/windows/crossdesk_session_helper.rc", {defines = windows_helper_resource_defines}) add_includedirs("apps/desktop/src/common", - "apps/desktop/src/platform/windows/service") + "apps/desktop/src/platform/windows/service", + "apps/desktop/src/platform/windows/screen_capturer") add_includedirs("apps/desktop/src/platform/windows/input") + add_includedirs("libs/wire/include") end target("crossdesk") @@ -482,7 +485,8 @@ function setup_targets() if is_os("windows") then add_files("apps/desktop/src/platform/windows/daemon_backend.cpp") add_files("apps/desktop/src/platform/windows/service/service_host.cpp") - add_includedirs("apps/desktop/src/platform/windows/service") + add_includedirs("apps/desktop/src/platform/windows/service", + "apps/desktop/src/platform/windows/screen_capturer") add_links("Advapi32", "Wtsapi32", "Ole32", "Userenv") add_deps("wgc_plugin", "crossdesk_service", "crossdesk_session_helper") add_files(crossdesk_windows_resource, {defines = windows_resource_defines})