mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-17 09:23:10 +08:00
[fix] correct WGC and GDI cursor capture and positioning
This commit is contained in:
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user