Compare commits

...
5 Commits
16 changed files with 402 additions and 71 deletions
+9 -1
View File
@@ -22,6 +22,7 @@
#include "config_center.h"
#include "daemon.h"
#include "path_manager.h"
#include "rd_log.h"
#include "render.h"
#ifdef __linux__
@@ -192,6 +193,14 @@ int main(int argc, char* argv[]) {
std::strcmp(argv[1], crossdesk::kSlintRendererProbeArgument) == 0) {
return crossdesk::RunSlintRendererProbe();
}
#endif
// Service operations and config migration can log before the GUI starts.
// Configure the directory first, including CLI and daemon child paths.
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
crossdesk::InitLogger(path_manager->GetLogPath().string());
#ifdef _WIN32
if (argc > 1 && IsServiceCliCommand(argv[1])) {
return HandleServiceCliCommand(argv[1]);
}
@@ -218,7 +227,6 @@ int main(int argc, char* argv[]) {
#endif
bool enable_daemon = false;
auto path_manager = std::make_unique<crossdesk::PathManager>("CrossDesk");
if (path_manager) {
std::string cache_path = path_manager->GetCachePath().string();
crossdesk::ConfigCenter config_center(cache_path + "/config.ini");
@@ -950,10 +950,10 @@ int GuiApplication::Run() {
}
exec_log_path_ = path_manager_->GetLogPath().string();
dll_log_path_ = exec_log_path_;
InitializeLogger();
cache_path_ = path_manager_->GetCachePath().string();
config_center_ = std::make_unique<ConfigCenter>(cache_path_ + "/config.ini");
InitializeLogger();
LOG_INFO("CrossDesk version: {} (Slint UI)", CROSSDESK_VERSION);
strncpy(signal_server_ip_self_, config_center_->GetSignalServerHost().c_str(),
@@ -1,11 +1,12 @@
#include "features/devices/session_device_manager.h"
#include <display_stream_id.h>
#include <remote_action.h>
#include <algorithm>
#include <chrono>
#include <cstdlib>
#include <display_stream_id.h>
#include "platform.h"
#include "rd_log.h"
#include "runtime/gui_runtime.h"
@@ -59,6 +60,53 @@ bool SessionDeviceManager::ShouldSendCapturedFrame(
return true;
}
void SessionDeviceManager::RecordCaptureCadence(
std::chrono::steady_clock::time_point now, int fps,
bool from_secure_desktop) {
std::lock_guard<std::mutex> lock(capture_metrics_mutex_);
if (capture_metrics_started_ == std::chrono::steady_clock::time_point{})
capture_metrics_started_ = now;
const auto elapsed_us = std::chrono::duration_cast<std::chrono::microseconds>(
now - capture_metrics_started_)
.count();
if (elapsed_us >= 2000000) {
if (capture_forwarded_) {
const char* source = capture_secure_callbacks_ == 0 ? "normal_desktop"
: capture_secure_callbacks_ == capture_callbacks_
? "secure_desktop"
: "mixed";
LOG_INFO(
"Capture delivery: source={} normal_callbacks={} secure_callbacks={} "
"target_fps={} input_fps={:.2f} "
"forwarded_fps={:.2f} callbacks={} rate_dropped={} "
"forwarded={} send_errors={} max_callback_gap_us={} "
"send_avg_us={} send_max_us={} window_ms={}",
source, capture_callbacks_ - capture_secure_callbacks_,
capture_secure_callbacks_, fps,
capture_callbacks_ * 1000000.0 / elapsed_us,
capture_forwarded_ * 1000000.0 / elapsed_us, capture_callbacks_,
capture_rate_drops_, capture_forwarded_, capture_send_errors_,
capture_max_gap_us_, capture_send_us_ / capture_forwarded_,
capture_max_send_us_, elapsed_us / 1000);
}
capture_metrics_started_ = now;
capture_callbacks_ = capture_rate_drops_ = capture_forwarded_ = 0;
capture_secure_callbacks_ = 0;
capture_send_errors_ = 0;
capture_max_gap_us_ = capture_send_us_ = capture_max_send_us_ = 0;
}
if (last_capture_callback_ != std::chrono::steady_clock::time_point{}) {
capture_max_gap_us_ =
std::max<int64_t>(capture_max_gap_us_,
std::chrono::duration_cast<std::chrono::microseconds>(
now - last_capture_callback_)
.count());
}
last_capture_callback_ = std::max(last_capture_callback_, now);
++capture_callbacks_;
if (from_secure_desktop) ++capture_secure_callbacks_;
}
void SessionDeviceManager::Initialize() {
InitializeAudioOutput();
screen_capturer_factory_ = new ScreenCapturerFactory();
@@ -88,6 +136,11 @@ int SessionDeviceManager::InitializeScreenCapturer() {
last_frame_time_ = {};
next_frame_deadline_ = {};
capture_metrics_started_ = last_capture_callback_ = {};
capture_callbacks_ = capture_rate_drops_ = capture_forwarded_ = 0;
capture_secure_callbacks_ = 0;
capture_send_errors_ = 0;
capture_max_gap_us_ = capture_send_us_ = capture_max_send_us_ = 0;
const int fps = owner_.config_center_->GetVideoFrameRate() ==
ConfigCenter::VIDEO_FRAME_RATE::FPS_30
? 30
@@ -103,7 +156,15 @@ int SessionDeviceManager::InitializeScreenCapturer() {
const char *display_name,
const MiniRtcNativeVideoFrame *native_frame) {
const auto now_time = std::chrono::steady_clock::now();
bool from_secure_desktop = false;
#ifdef _WIN32
from_secure_desktop =
ScreenCapturerWin::CurrentFrameIsFromSecureDesktop();
#endif
RecordCaptureCadence(now_time, fps, from_secure_desktop);
if (!ShouldSendCapturedFrame(now_time, fps)) {
std::lock_guard<std::mutex> lock(capture_metrics_mutex_);
++capture_rate_drops_;
return;
}
@@ -170,9 +231,24 @@ int SessionDeviceManager::InitializeScreenCapturer() {
frame.height = height;
frame.captured_timestamp = GetSystemTimeMicros(owner_.peer_);
frame.native_frame = native_frame;
const auto send_started = std::chrono::steady_clock::now();
uint64_t send_errors = 0;
for (const std::string &remote_id : connected_remote_ids) {
SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
remote_id.data(), remote_id.size());
if (SendVideoFrameToPeer(owner_.peer_, &frame, stream_id.c_str(),
remote_id.data(), remote_id.size()) != 0)
++send_errors;
}
const auto send_us =
std::chrono::duration_cast<std::chrono::microseconds>(
std::chrono::steady_clock::now() - send_started)
.count();
{
std::lock_guard<std::mutex> lock(capture_metrics_mutex_);
++capture_forwarded_;
capture_send_errors_ += send_errors;
capture_send_us_ += send_us;
capture_max_send_us_ =
std::max<int64_t>(capture_max_send_us_, send_us);
}
last_video_frame_stream_id_ = stream_id;
last_frame_time_ = now_time;
@@ -84,6 +84,8 @@ private:
void ClearCapturedKeyboardInput();
bool ShouldSendCapturedFrame(std::chrono::steady_clock::time_point now,
int fps);
void RecordCaptureCadence(std::chrono::steady_clock::time_point now, int fps,
bool from_secure_desktop);
GuiRuntime &owner_;
SDL_AudioStream *output_stream_ = nullptr;
@@ -99,6 +101,14 @@ private:
std::mutex captured_keyboard_inputs_mutex_;
std::chrono::steady_clock::time_point last_frame_time_{};
std::chrono::steady_clock::time_point next_frame_deadline_{};
std::mutex capture_metrics_mutex_;
std::chrono::steady_clock::time_point capture_metrics_started_{};
std::chrono::steady_clock::time_point last_capture_callback_{};
uint64_t capture_callbacks_ = 0, capture_rate_drops_ = 0;
uint64_t capture_secure_callbacks_ = 0;
uint64_t capture_forwarded_ = 0, capture_send_errors_ = 0;
int64_t capture_max_gap_us_ = 0;
int64_t capture_send_us_ = 0, capture_max_send_us_ = 0;
std::string last_video_frame_stream_id_;
bool invalid_video_stream_id_logged_ = false;
};
@@ -6,37 +6,12 @@
#include <windows.h>
#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 {};
@@ -47,16 +22,33 @@ bool CursorStateProvider::Sample(const std::vector<DisplayInfo>& 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<RemoteCursorShape>(secure_cursor.shape)
: RemoteCursorShape::none;
return true;
}
CURSORINFO info{};
info.cbSize = sizeof(info);
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;
}
+26 -13
View File
@@ -1,6 +1,5 @@
#include "rd_log.h"
#include <atomic>
#include <filesystem>
namespace crossdesk {
@@ -9,26 +8,38 @@ namespace {
std::string g_log_dir = "logs";
std::once_flag g_logger_once_flag;
std::mutex g_logger_mutex;
std::shared_ptr<spdlog::logger> g_logger;
std::atomic<bool> g_logger_created{false};
std::string NormalizeLogDirectory(const std::string& log_dir) {
const std::filesystem::path path = log_dir.empty() ? "logs" : log_dir;
std::error_code ec;
const auto absolute = std::filesystem::absolute(path, ec);
return (ec ? path : absolute).lexically_normal().string();
}
} // namespace
void InitLogger(const std::string& log_dir) {
if (g_logger_created.load()) {
LOG_WARN(
"InitLogger called after logger initialized. Ignoring log_dir: {}, "
"using previous log_dir: {}",
log_dir, g_log_dir);
const auto directory = NormalizeLogDirectory(log_dir);
std::lock_guard<std::mutex> lock(g_logger_mutex);
if (g_logger) {
if (directory != g_log_dir) {
g_logger->warn(
"InitLogger called after logger initialized. Ignoring log_dir: {}, "
"using previous log_dir: {}",
directory, g_log_dir);
}
return;
}
g_log_dir = log_dir;
g_log_dir = directory;
}
std::shared_ptr<spdlog::logger> get_logger() {
std::call_once(g_logger_once_flag, []() {
g_logger_created.store(true);
std::lock_guard<std::mutex> lock(g_logger_mutex);
g_log_dir = NormalizeLogDirectory(g_log_dir);
std::error_code ec;
std::filesystem::create_directories(g_log_dir, ec);
@@ -54,10 +65,12 @@ std::shared_ptr<spdlog::logger> get_logger() {
sinks.push_back(std::make_shared<spdlog::sinks::rotating_file_sink_mt>(
filename, 5 * 1024 * 1024, 3));
g_logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(),
sinks.end());
g_logger->flush_on(spdlog::level::info);
spdlog::register_logger(g_logger);
auto logger = std::make_shared<spdlog::logger>(LOGGER_NAME, sinks.begin(),
sinks.end());
logger->flush_on(spdlog::level::info);
spdlog::register_logger(logger);
logger->info("Logger initialized: path={}", filename);
g_logger = std::move(logger);
});
return g_logger;
@@ -0,0 +1,32 @@
#ifndef CROSSDESK_WINDOWS_CURSOR_SHAPE_H_
#define CROSSDESK_WINDOWS_CURSOR_SHAPE_H_
#include <windows.h>
#include <remote_cursor_shape.h>
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_
@@ -0,0 +1,48 @@
#ifndef CROSSDESK_DXGI_CURSOR_STATE_H_
#define CROSSDESK_DXGI_CURSOR_STATE_H_
#include <atomic>
#include <cstdint>
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<void*> embedded_monitor_{nullptr};
};
inline DxgiCursorState& SharedDxgiCursorState() {
static DxgiCursorState state;
return state;
}
} // namespace crossdesk
#endif // CROSSDESK_DXGI_CURSOR_STATE_H_
@@ -7,6 +7,7 @@
#include <string>
#include <vector>
#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<IDXGIOutput1> 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<int>(display_info_list_.size())) {
SharedDxgiCursorState().Update(
frame_info.LastMouseUpdateTime.QuadPart,
frame_info.PointerPosition.Visible != FALSE,
display_info_list_[frame_monitor].handle);
}
Microsoft::WRL::ComPtr<ID3D11Texture2D> acquired_tex;
if (desktop_resource) {
hr = desktop_resource->QueryInterface(
@@ -16,11 +16,13 @@
#include <display_stream_id.h>
#include "captured_nv12_frame.h"
#include "dxgi_cursor_state.h"
#include "interactive_state.h"
#include "named_pipe_deadline.h"
#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"
@@ -33,6 +35,8 @@ namespace {
using Json = nlohmann::json;
thread_local bool current_frame_from_secure_desktop = false;
constexpr DWORD kSecureDesktopStatusIntervalMs = 250;
constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 500;
constexpr DWORD kSecureDesktopHelperPipeTimeoutMs = 120;
@@ -222,6 +226,7 @@ bool ParseSecureDesktopFrameResponse(const std::vector<uint8_t>& response,
*width_out = static_cast<int>(header.width);
*height_out = static_cast<int>(header.height);
nv12_frame_out->assign(response.begin() + sizeof(header), response.end());
SharedSecureDesktopCursorState().Update(header.cursor);
return true;
}
@@ -362,6 +367,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;
}
@@ -460,10 +466,17 @@ int ScreenCapturerWin::Destroy() {
return 0;
}
bool ScreenCapturerWin::CurrentFrameIsFromSecureDesktop() {
return current_frame_from_secure_desktop;
}
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()) {
@@ -473,6 +486,14 @@ void ScreenCapturerWin::EmitCapturedFrame(
return;
}
// Normal and helper callbacks can originate on different threads during
// desktop transitions. Keep diagnostics tied to this callback's source.
struct RestoreFrameSource {
bool previous;
~RestoreFrameSource() { current_frame_from_secure_desktop = previous; }
} restore_source{
std::exchange(current_frame_from_secure_desktop, from_secure_desktop)};
if (native_frame) {
if (!native_output_logged_.exchange(true, std::memory_order_relaxed)) {
LOG_INFO("Windows capturer native frame output enabled (type={})",
@@ -593,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);
@@ -618,6 +640,7 @@ int ScreenCapturerWin::Stop() {
ret = impl_->Stop();
}
StopSecureCaptureThread();
SharedSecureDesktopCursorState().SetActive(false);
StopSecureDesktopSharedCapture(secure_shared_session_id_);
return ret;
}
@@ -999,6 +1022,9 @@ bool ScreenCapturerWin::ReadSecureDesktopSharedFrame(
}
nv12_frame_out->resize(payload_size);
const SecureDesktopCursorSnapshot cursor = header->cursor;
const int captured_width = static_cast<int>(header->width);
const int captured_height = static_cast<int>(header->height);
std::memcpy(nv12_frame_out->data(), secure_frame_view_ + sizeof(*header),
payload_size);
MemoryBarrier();
@@ -1009,8 +1035,9 @@ bool ScreenCapturerWin::ReadSecureDesktopSharedFrame(
return false;
}
*width_out = static_cast<int>(header->width);
*height_out = static_cast<int>(header->height);
*width_out = captured_width;
*height_out = captured_height;
SharedSecureDesktopCursorState().Update(cursor);
return true;
}
@@ -1111,6 +1138,18 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
max_status_ms = 0;
};
std::vector<uint8_t> secure_frame;
ULONGLONG last_shared_error_tick = 0;
auto report_shared_error = [&](const char* operation,
const std::string& error) {
const ULONGLONG now = GetTickCount64();
if (last_shared_error_tick == 0 || now - last_shared_error_tick >= 5000) {
LOG_WARN(
"Windows capturer secure shared capture {} failed; using pipe "
"fallback, stage='{}', session_id={}, error={}",
operation, status.interactive_stage, status.active_session_id, error);
last_shared_error_tick = now;
}
};
while (running_.load(std::memory_order_relaxed)) {
// Privacy covers belong to the ordinary desktop. Drop privacy on a
@@ -1153,6 +1192,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 ||
@@ -1257,6 +1297,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
status.interactive_stage, status.interactive_desktop, show_cursor,
shared_fps, &error_message);
if (!shared_ready) {
report_shared_error("start", error_message);
frame_schedule.OnStartFailure(GetTickCount64());
} else if (!was_started) {
frame_schedule.Reset(GetTickCount64());
@@ -1276,6 +1317,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
IsPendingSecureDesktopFrame(error_message);
if (frame_pending) ++shared_waits;
if (shared_ready && !frame_delivered && !frame_pending) {
report_shared_error("read", error_message);
StopSecureDesktopSharedCapture(secure_shared_session_id_);
frame_schedule.OnStartFailure(GetTickCount64());
}
@@ -1339,6 +1381,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
@@ -48,6 +48,9 @@ class ScreenCapturerWin : public ScreenCapturer {
void SetPrivacyController(PrivacyController* privacy) override { privacy_ = privacy; }
// Set before Init(), while capture is stopped.
void SetCaptureMethod(ScreenCaptureMethod method) { capture_method_ = method; }
// Query synchronously inside the frame callback, on the delivering thread.
// This describes the actual frame, independently of service status polling.
static bool CurrentFrameIsFromSecureDesktop();
private:
std::unique_ptr<ScreenCapturer> impl_;
@@ -0,0 +1,56 @@
#ifndef CROSSDESK_SECURE_DESKTOP_CURSOR_STATE_H_
#define CROSSDESK_SECURE_DESKTOP_CURSOR_STATE_H_
#include <cstdint>
#include <mutex>
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<std::mutex> lock(mutex_);
if (active_ != active) snapshot_ = {};
active_ = active;
}
void Update(const SecureDesktopCursorSnapshot& snapshot) {
std::lock_guard<std::mutex> lock(mutex_);
if (active_ && snapshot.valid) snapshot_ = snapshot;
}
bool Get(SecureDesktopCursorSnapshot* snapshot) const {
std::lock_guard<std::mutex> 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_
@@ -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 {
@@ -97,18 +98,18 @@ struct SecureSharedCaptureState {
uint32_t sequence = 0;
};
struct PipeSecurityAttributes {
PipeSecurityAttributes() = default;
~PipeSecurityAttributes() {
struct IpcSecurityAttributes {
IpcSecurityAttributes() = default;
~IpcSecurityAttributes() {
if (security_descriptor_ != nullptr) {
LocalFree(security_descriptor_);
}
}
bool Initialize() {
constexpr wchar_t kPipeSddl[] = L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)";
bool Initialize(const wchar_t* sddl =
L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;GRGW;;;AU)") {
if (!ConvertStringSecurityDescriptorToSecurityDescriptorW(
kPipeSddl, SDDL_REVISION_1, &security_descriptor_, nullptr)) {
sddl, SDDL_REVISION_1, &security_descriptor_, nullptr)) {
return false;
}
@@ -452,7 +453,7 @@ std::string BuildHelperStatusResponse(HelperState* helper_state) {
void HelperIpcServerLoop(HANDLE stop_event, DWORD session_id,
HelperState* helper_state) {
PipeSecurityAttributes security_attributes;
IpcSecurityAttributes security_attributes;
SECURITY_ATTRIBUTES* pipe_attributes = nullptr;
if (security_attributes.Initialize()) {
pipe_attributes = security_attributes.get();
@@ -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<uint32_t>(
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<uint8_t> CaptureSecureDesktopFrame(
header.height = static_cast<uint32_t>(request.height);
header.payload_size =
static_cast<uint32_t>(capture_buffers->nv12_frame.size());
header.cursor = capture.cursor_snapshot;
std::vector<uint8_t> response(sizeof(header) +
capture_buffers->nv12_frame.size());
@@ -1362,6 +1379,7 @@ void SecureDesktopSharedCaptureThread(SecureSharedCaptureState* capture_state) {
header->width = static_cast<uint32_t>(request.width);
header->height = static_cast<uint32_t>(request.height);
header->payload_size = static_cast<uint32_t>(nv12_frame.size());
header->cursor = capture->cursor_snapshot;
std::memcpy(payload, nv12_frame.data(), nv12_frame.size());
header->sequence = ++capture_state->sequence;
MemoryBarrier();
@@ -1453,12 +1471,21 @@ std::vector<uint8_t> StartSecureDesktopSharedCapture(
return BuildTextResponseBytes(BuildErrorJson("invalid_capture_size"));
}
PipeSecurityAttributes security_attributes;
IpcSecurityAttributes security_attributes;
SECURITY_ATTRIBUTES* attributes = nullptr;
if (security_attributes.Initialize()) {
attributes = security_attributes.get();
}
// Event GENERIC_READ/GENERIC_WRITE omit SYNCHRONIZE. The GUI only needs
// to wait for frames; the SYSTEM helper retains permission to signal them.
IpcSecurityAttributes event_security_attributes;
if (!event_security_attributes.Initialize(
L"D:(A;;GA;;;SY)(A;;GA;;;BA)(A;;0x00100000;;;AU)")) {
return BuildTextResponseBytes(
BuildErrorJson("create_frame_event_security_failed", GetLastError()));
}
const std::wstring mapping_name =
crossdesk::GetCrossDeskSecureDesktopFrameMappingName(
capture_state->session_id);
@@ -1485,7 +1512,8 @@ std::vector<uint8_t> StartSecureDesktopSharedCapture(
}
HANDLE frame_ready_event =
CreateEventW(attributes, FALSE, FALSE, event_name.c_str());
CreateEventW(event_security_attributes.get(), FALSE, FALSE,
event_name.c_str());
if (frame_ready_event == nullptr) {
const DWORD error = GetLastError();
UnmapViewOfFile(frame_view);
@@ -1647,7 +1675,7 @@ void SecureInputHelperIpcServerLoop(HANDLE stop_event, DWORD session_id) {
auto capture_state = std::make_shared<SecureSharedCaptureState>();
capture_state->session_id = session_id;
PipeSecurityAttributes security_attributes;
IpcSecurityAttributes security_attributes;
SECURITY_ATTRIBUTES* pipe_attributes = nullptr;
if (security_attributes.Initialize()) {
pipe_attributes = security_attributes.get();
@@ -12,6 +12,8 @@
#include <cstdint>
#include <string>
#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)
+7 -3
View File
@@ -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})