From 52b894fe0edf73e050f147686d2bc8b69fefbdea Mon Sep 17 00:00:00 2001 From: dijunkun Date: Tue, 26 May 2026 01:28:12 +0800 Subject: [PATCH] [feat] improve secure desktop capture by streaming latest frames through shared memory --- .../windows/screen_capturer_win.cpp | 321 +++++++++++++++- .../windows/screen_capturer_win.h | 27 +- src/service/windows/session_helper_main.cpp | 359 +++++++++++++++++- src/service/windows/session_helper_shared.h | 35 +- 4 files changed, 715 insertions(+), 27 deletions(-) diff --git a/src/screen_capturer/windows/screen_capturer_win.cpp b/src/screen_capturer/windows/screen_capturer_win.cpp index 3a38256..e56046a 100644 --- a/src/screen_capturer/windows/screen_capturer_win.cpp +++ b/src/screen_capturer/windows/screen_capturer_win.cpp @@ -33,7 +33,9 @@ constexpr DWORD kSecureDesktopStatusPipeTimeoutMs = 150; constexpr DWORD kSecureDesktopHelperPipeTimeoutMs = 120; constexpr DWORD kSecureDesktopTransientErrorGraceMs = 1500; constexpr DWORD kSecureDesktopTransientErrorLogIntervalMs = 5000; -constexpr int kSecureDesktopCaptureMinIntervalMs = 100; +constexpr int kSecureDesktopCaptureMinFps = 30; +constexpr int kSecureDesktopCaptureMaxIntervalMs = + 1000 / kSecureDesktopCaptureMinFps; struct SecureDesktopServiceStatus { bool service_available = false; @@ -136,6 +138,16 @@ std::string BuildSecureCaptureCommand(int left, int top, int width, int height, return stream.str(); } +std::string BuildSecureCaptureStartCommand(int left, int top, int width, + int height, bool show_cursor, + int fps) { + std::ostringstream stream; + stream << kCrossDeskSecureInputCaptureStartCommandPrefix << left << ":" << top + << ":" << width << ":" << height << ":" << (show_cursor ? 1 : 0) + << ":" << fps; + return stream.str(); +} + std::string ExtractPipeTextResponse(const std::vector& response) { if (response.empty() || response.front() != '{') { return ""; @@ -274,17 +286,15 @@ bool QuerySecureDesktopServiceStatus(SecureDesktopServiceStatus* status) { return true; } -bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, - int width, int height, bool show_cursor, - std::vector* nv12_frame_out, - int* captured_width_out, - int* captured_height_out, - std::string* error_out) { - if (nv12_frame_out == nullptr || captured_width_out == nullptr || - captured_height_out == nullptr) { +bool QuerySecureDesktopHelperCommand(DWORD session_id, + const std::string& command, + std::vector* response_out, + std::string* error_out) { + if (response_out == nullptr) { return false; } + response_out->clear(); const std::wstring pipe_name = GetCrossDeskSecureInputHelperPipeName(session_id); if (!WaitNamedPipeW(pipe_name.c_str(), kSecureDesktopHelperPipeTimeoutMs)) { @@ -306,8 +316,6 @@ bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, DWORD pipe_mode = PIPE_READMODE_MESSAGE; SetNamedPipeHandleState(pipe, &pipe_mode, nullptr, nullptr); - const std::string command = - BuildSecureCaptureCommand(left, top, width, height, show_cursor); DWORD bytes_written = 0; if (!WriteFile(pipe, command.data(), static_cast(command.size()), &bytes_written, nullptr)) { @@ -319,9 +327,8 @@ bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, return false; } - std::vector response; DWORD read_error = 0; - const bool read_ok = ReadPipeMessage(pipe, &response, &read_error); + const bool read_ok = ReadPipeMessage(pipe, response_out, &read_error); CloseHandle(pipe); if (!read_ok) { if (error_out != nullptr) { @@ -330,6 +337,28 @@ bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, return false; } + return true; +} + +bool QuerySecureDesktopHelperFrame(DWORD session_id, int left, int top, + int width, int height, bool show_cursor, + std::vector* nv12_frame_out, + int* captured_width_out, + int* captured_height_out, + std::string* error_out) { + if (nv12_frame_out == nullptr || captured_width_out == nullptr || + captured_height_out == nullptr) { + return false; + } + + const std::string command = + BuildSecureCaptureCommand(left, top, width, height, show_cursor); + std::vector response; + if (!QuerySecureDesktopHelperCommand(session_id, command, &response, + error_out)) { + return false; + } + return ParseSecureDesktopFrameResponse(response, nv12_frame_out, captured_width_out, captured_height_out, error_out); @@ -496,6 +525,7 @@ int ScreenCapturerWin::Stop() { ret = impl_->Stop(); } StopSecureCaptureThread(); + StopSecureDesktopSharedCapture(secure_shared_session_id_); return ret; } @@ -616,10 +646,234 @@ bool ScreenCapturerWin::GetCurrentCaptureRegion(int* left, int* top, int* width, return true; } +void ScreenCapturerWin::CloseSecureDesktopSharedFrame() { + if (secure_frame_view_ != nullptr) { + UnmapViewOfFile(secure_frame_view_); + secure_frame_view_ = nullptr; + } + if (secure_frame_ready_event_ != nullptr) { + CloseHandle(secure_frame_ready_event_); + secure_frame_ready_event_ = nullptr; + } + if (secure_frame_mapping_ != nullptr) { + CloseHandle(secure_frame_mapping_); + secure_frame_mapping_ = nullptr; + } + secure_frame_view_size_ = 0; +} + +void ScreenCapturerWin::StopSecureDesktopSharedCapture(DWORD session_id) { + DWORD target_session_id = session_id; + if (target_session_id == 0xFFFFFFFF) { + target_session_id = secure_shared_session_id_; + } + + if (secure_shared_capture_started_ && + target_session_id != 0xFFFFFFFF) { + std::vector response; + std::string error_message; + QuerySecureDesktopHelperCommand( + target_session_id, kCrossDeskSecureInputCaptureStopCommand, &response, + &error_message); + } + + CloseSecureDesktopSharedFrame(); + secure_shared_capture_started_ = false; + secure_shared_session_id_ = 0xFFFFFFFF; + secure_shared_left_ = 0; + secure_shared_top_ = 0; + secure_shared_width_ = 0; + secure_shared_height_ = 0; + secure_shared_fps_ = 0; + secure_shared_show_cursor_ = true; +} + +bool ScreenCapturerWin::OpenSecureDesktopSharedFrame(DWORD session_id, + size_t min_size, + std::string* error_out) { + if (secure_frame_view_ != nullptr && + secure_shared_session_id_ == session_id && + secure_frame_view_size_ >= min_size) { + return true; + } + + CloseSecureDesktopSharedFrame(); + + const std::wstring mapping_name = + GetCrossDeskSecureDesktopFrameMappingName(session_id); + HANDLE frame_mapping = + OpenFileMappingW(FILE_MAP_READ, FALSE, mapping_name.c_str()); + if (frame_mapping == nullptr) { + if (error_out != nullptr) { + *error_out = "open_frame_mapping_failed:" + + std::to_string(GetLastError()); + } + return false; + } + + auto* frame_view = + static_cast(MapViewOfFile(frame_mapping, FILE_MAP_READ, 0, 0, 0)); + if (frame_view == nullptr) { + const DWORD error = GetLastError(); + CloseHandle(frame_mapping); + if (error_out != nullptr) { + *error_out = "map_frame_view_failed:" + std::to_string(error); + } + return false; + } + + const std::wstring event_name = + GetCrossDeskSecureDesktopFrameReadyEventName(session_id); + HANDLE frame_ready_event = + OpenEventW(SYNCHRONIZE, FALSE, event_name.c_str()); + if (frame_ready_event == nullptr) { + const DWORD error = GetLastError(); + UnmapViewOfFile(frame_view); + CloseHandle(frame_mapping); + if (error_out != nullptr) { + *error_out = "open_frame_event_failed:" + std::to_string(error); + } + return false; + } + + secure_frame_mapping_ = frame_mapping; + secure_frame_ready_event_ = frame_ready_event; + secure_frame_view_ = frame_view; + secure_frame_view_size_ = min_size; + secure_shared_session_id_ = session_id; + return true; +} + +bool ScreenCapturerWin::ReadSecureDesktopSharedFrame( + DWORD wait_ms, std::vector* nv12_frame_out, int* width_out, + int* height_out, std::string* error_out) { + if (nv12_frame_out == nullptr || width_out == nullptr || + height_out == nullptr || secure_frame_view_ == nullptr || + secure_frame_ready_event_ == nullptr) { + return false; + } + + const DWORD wait_result = WaitForSingleObject(secure_frame_ready_event_, + wait_ms); + if (wait_result == WAIT_TIMEOUT) { + if (error_out != nullptr) { + *error_out = "frame_wait_timeout"; + } + return false; + } + if (wait_result != WAIT_OBJECT_0) { + if (error_out != nullptr) { + *error_out = "frame_wait_failed:" + std::to_string(GetLastError()); + } + return false; + } + + auto* header = + reinterpret_cast( + secure_frame_view_); + if (header->magic != kCrossDeskSecureDesktopFrameMagic || + header->version != kCrossDeskSecureDesktopFrameVersion) { + if (error_out != nullptr) { + *error_out = "invalid_shared_frame_header"; + } + return false; + } + if (header->writing != 0) { + if (error_out != nullptr) { + *error_out = "shared_frame_write_in_progress"; + } + return false; + } + + const uint32_t sequence = header->sequence; + const uint32_t payload_size = header->payload_size; + const uint32_t buffer_size = header->buffer_size; + if (payload_size == 0 || payload_size > buffer_size || + sizeof(*header) + static_cast(payload_size) > + secure_frame_view_size_) { + if (error_out != nullptr) { + *error_out = "invalid_shared_frame_size"; + } + return false; + } + + nv12_frame_out->resize(payload_size); + std::memcpy(nv12_frame_out->data(), secure_frame_view_ + sizeof(*header), + payload_size); + MemoryBarrier(); + if (header->writing != 0 || header->sequence != sequence) { + if (error_out != nullptr) { + *error_out = "shared_frame_changed_during_read"; + } + return false; + } + + *width_out = static_cast(header->width); + *height_out = static_cast(header->height); + return true; +} + +bool ScreenCapturerWin::StartSecureDesktopSharedCapture( + DWORD session_id, int left, int top, int width, int height, + bool show_cursor, int fps, std::string* error_out) { + const size_t payload_size = static_cast(width) * height * 3 / 2; + const size_t mapping_size = + sizeof(CrossDeskSecureDesktopSharedFrameHeader) + payload_size; + if (payload_size == 0) { + if (error_out != nullptr) { + *error_out = "invalid_capture_size"; + } + return false; + } + + if (secure_shared_capture_started_ && + secure_shared_session_id_ == session_id && + secure_shared_left_ == left && secure_shared_top_ == top && + secure_shared_width_ == width && secure_shared_height_ == height && + secure_shared_show_cursor_ == show_cursor && secure_shared_fps_ == fps && + OpenSecureDesktopSharedFrame(session_id, mapping_size, error_out)) { + return true; + } + + StopSecureDesktopSharedCapture(secure_shared_session_id_); + + const std::string command = + BuildSecureCaptureStartCommand(left, top, width, height, show_cursor, fps); + std::vector response; + if (!QuerySecureDesktopHelperCommand(session_id, command, &response, + error_out)) { + return false; + } + + Json json = Json::parse(response.begin(), response.end(), nullptr, false); + if (json.is_discarded() || !json.value("ok", false)) { + if (error_out != nullptr) { + *error_out = ExtractPipeTextResponse(response); + } + return false; + } + + secure_shared_capture_started_ = true; + secure_shared_session_id_ = session_id; + secure_shared_left_ = left; + secure_shared_top_ = top; + secure_shared_width_ = width; + secure_shared_height_ = height; + secure_shared_show_cursor_ = show_cursor; + secure_shared_fps_ = fps; + + if (!OpenSecureDesktopSharedFrame(session_id, mapping_size, error_out)) { + StopSecureDesktopSharedCapture(session_id); + return false; + } + + return true; +} + void ScreenCapturerWin::SecureDesktopCaptureLoop() { const int frame_interval_ms = - fps_ > 0 ? (std::max)(kSecureDesktopCaptureMinIntervalMs, 1000 / fps_) - : kSecureDesktopCaptureMinIntervalMs; + fps_ > 0 ? (std::min)(kSecureDesktopCaptureMaxIntervalMs, 1000 / fps_) + : kSecureDesktopCaptureMaxIntervalMs; ULONGLONG last_status_tick = 0; ULONGLONG last_error_tick = 0; bool last_capture_active = false; @@ -686,12 +940,14 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { } if (!status.capture_active || status.active_session_id == 0xFFFFFFFF) { + StopSecureDesktopSharedCapture(secure_shared_session_id_); std::this_thread::sleep_for( std::chrono::milliseconds(status.service_available ? 50 : 200)); continue; } if (!status.helper_running) { + StopSecureDesktopSharedCapture(secure_shared_session_id_); std::this_thread::sleep_for(std::chrono::milliseconds(30)); continue; } @@ -702,6 +958,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { int height = 0; std::string display_name; if (!GetCurrentCaptureRegion(&left, &top, &width, &height, &display_name)) { + StopSecureDesktopSharedCapture(secure_shared_session_id_); std::this_thread::sleep_for(std::chrono::milliseconds(100)); continue; } @@ -709,15 +966,38 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { int captured_width = 0; int captured_height = 0; std::string error_message; - if (QuerySecureDesktopHelperFrame( - status.active_session_id, left, top, width, height, - show_cursor_.load(std::memory_order_relaxed), &secure_frame, + bool frame_delivered = false; + const bool show_cursor = show_cursor_.load(std::memory_order_relaxed); + const int shared_fps = + fps_ > 0 ? (std::max)(kSecureDesktopCaptureMinFps, fps_) + : kSecureDesktopCaptureMinFps; + + if (StartSecureDesktopSharedCapture(status.active_session_id, left, top, + width, height, show_cursor, shared_fps, + &error_message) && + ReadSecureDesktopSharedFrame( + static_cast(frame_interval_ms + 20), &secure_frame, &captured_width, &captured_height, &error_message)) { if (cb_orig_ && !secure_frame.empty()) { cb_orig_(secure_frame.data(), static_cast(secure_frame.size()), captured_width, captured_height, display_name.c_str()); } - } else { + frame_delivered = true; + } + + if (!frame_delivered && + QuerySecureDesktopHelperFrame(status.active_session_id, left, top, + width, height, show_cursor, + &secure_frame, &captured_width, + &captured_height, &error_message)) { + if (cb_orig_ && !secure_frame.empty()) { + cb_orig_(secure_frame.data(), static_cast(secure_frame.size()), + captured_width, captured_height, display_name.c_str()); + } + frame_delivered = true; + } + + if (!frame_delivered) { const bool transient_error = IsTransientSecureDesktopFrameError(error_message); const bool in_grace_period = capture_stage_started_tick != 0 && @@ -742,7 +1022,8 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() { std::this_thread::sleep_for(std::chrono::milliseconds(frame_interval_ms)); } + StopSecureDesktopSharedCapture(secure_shared_session_id_); secure_desktop_capture_active_.store(false, std::memory_order_relaxed); } -} // namespace crossdesk \ No newline at end of file +} // namespace crossdesk diff --git a/src/screen_capturer/windows/screen_capturer_win.h b/src/screen_capturer/windows/screen_capturer_win.h index b6cb5d8..e86b583 100644 --- a/src/screen_capturer/windows/screen_capturer_win.h +++ b/src/screen_capturer/windows/screen_capturer_win.h @@ -10,6 +10,7 @@ #include #include +#include #include #include #include @@ -59,6 +60,18 @@ class ScreenCapturerWin : public ScreenCapturer { int initial_monitor_index_ = 0; std::atomic secure_desktop_capture_active_{false}; std::thread secure_capture_thread_; + HANDLE secure_frame_mapping_ = nullptr; + HANDLE secure_frame_ready_event_ = nullptr; + uint8_t* secure_frame_view_ = nullptr; + size_t secure_frame_view_size_ = 0; + DWORD secure_shared_session_id_ = 0xFFFFFFFF; + int secure_shared_left_ = 0; + int secure_shared_top_ = 0; + int secure_shared_width_ = 0; + int secure_shared_height_ = 0; + int secure_shared_fps_ = 0; + bool secure_shared_show_cursor_ = true; + bool secure_shared_capture_started_ = false; void BuildCanonicalFromImpl(); void RebuildAliasesFromImpl(); @@ -66,6 +79,18 @@ class ScreenCapturerWin : public ScreenCapturer { void SecureDesktopCaptureLoop(); bool GetCurrentCaptureRegion(int* left, int* top, int* width, int* height, std::string* display_name); + bool StartSecureDesktopSharedCapture(DWORD session_id, int left, int top, + int width, int height, + bool show_cursor, int fps, + std::string* error_out); + void StopSecureDesktopSharedCapture(DWORD session_id); + bool OpenSecureDesktopSharedFrame(DWORD session_id, size_t min_size, + std::string* error_out); + bool ReadSecureDesktopSharedFrame(DWORD wait_ms, + std::vector* nv12_frame_out, + int* width_out, int* height_out, + std::string* error_out); + void CloseSecureDesktopSharedFrame(); }; } // namespace crossdesk -#endif \ No newline at end of file +#endif diff --git a/src/service/windows/session_helper_main.cpp b/src/service/windows/session_helper_main.cpp index 511196d..19670c9 100644 --- a/src/service/windows/session_helper_main.cpp +++ b/src/service/windows/session_helper_main.cpp @@ -6,9 +6,13 @@ #include #include +#include +#include +#include #include #include #include +#include #include #include #include @@ -53,6 +57,7 @@ struct SecureCaptureRequest { int width = 0; int height = 0; bool show_cursor = true; + int fps = 30; }; struct SecureMouseRequest { @@ -66,6 +71,19 @@ struct SecureCaptureBuffers { std::vector nv12_frame; }; +struct SecureSharedCaptureState { + std::mutex mutex; + std::thread capture_thread; + std::atomic stop_requested{false}; + DWORD session_id = 0xFFFFFFFF; + SecureCaptureRequest request; + HANDLE frame_mapping = nullptr; + HANDLE frame_ready_event = nullptr; + uint8_t* frame_view = nullptr; + size_t frame_view_size = 0; + uint32_t sequence = 0; +}; + struct PipeSecurityAttributes { PipeSecurityAttributes() = default; ~PipeSecurityAttributes() { @@ -718,6 +736,48 @@ bool ParseSecureInputCaptureCommand(const std::string& command, return request_out->width > 0 && request_out->height > 0; } +bool ParseSecureInputCaptureStartCommand(const std::string& command, + SecureCaptureRequest* request_out) { + if (request_out == nullptr) { + return false; + } + + if (command.rfind( + crossdesk::kCrossDeskSecureInputCaptureStartCommandPrefix, 0) != 0) { + return false; + } + + const size_t values_begin = std::strlen( + crossdesk::kCrossDeskSecureInputCaptureStartCommandPrefix); + int parsed_values[6] = {0}; + size_t token_begin = values_begin; + for (int index = 0; index < 6; ++index) { + const size_t separator = command.find(':', token_begin); + const bool is_last = index == 5; + const size_t token_end = is_last ? command.size() : separator; + if (token_end == std::string::npos || token_end <= token_begin) { + return false; + } + + try { + parsed_values[index] = + std::stoi(command.substr(token_begin, token_end - token_begin)); + } catch (...) { + return false; + } + + token_begin = token_end + 1; + } + + request_out->left = parsed_values[0]; + request_out->top = parsed_values[1]; + request_out->width = parsed_values[2] & ~1; + request_out->height = parsed_values[3] & ~1; + request_out->show_cursor = parsed_values[4] != 0; + request_out->fps = parsed_values[5] > 0 ? parsed_values[5] : 30; + return request_out->width > 0 && request_out->height > 0; +} + int InjectMouseInput(const SecureMouseRequest& request) { SetCursorPos(request.x, request.y); @@ -874,12 +934,292 @@ std::vector CaptureSecureDesktopFrame( return response; } +void CloseSecureDesktopSharedCaptureResourcesLocked( + SecureSharedCaptureState* capture_state) { + if (capture_state == nullptr) { + return; + } + + if (capture_state->frame_view != nullptr) { + UnmapViewOfFile(capture_state->frame_view); + capture_state->frame_view = nullptr; + } + if (capture_state->frame_ready_event != nullptr) { + CloseHandle(capture_state->frame_ready_event); + capture_state->frame_ready_event = nullptr; + } + if (capture_state->frame_mapping != nullptr) { + CloseHandle(capture_state->frame_mapping); + capture_state->frame_mapping = nullptr; + } + capture_state->frame_view_size = 0; +} + +void SecureDesktopSharedCaptureThread( + SecureSharedCaptureState* capture_state) { + if (capture_state == nullptr) { + return; + } + + SecureCaptureRequest request; + uint8_t* frame_view = nullptr; + { + std::lock_guard lock(capture_state->mutex); + request = capture_state->request; + frame_view = capture_state->frame_view; + } + + if (frame_view == nullptr || request.width <= 0 || request.height <= 0) { + return; + } + + const int interval_ms = + request.fps > 0 ? (std::max)(1, 1000 / request.fps) : 33; + const size_t nv12_size = + static_cast(request.width) * request.height * 3 / 2; + std::vector nv12_frame(nv12_size); + + HDC screen_dc = GetDC(nullptr); + if (screen_dc == nullptr) { + LOG_ERROR("Secure shared capture GetDC failed, error={}", GetLastError()); + return; + } + + HDC mem_dc = CreateCompatibleDC(screen_dc); + if (mem_dc == nullptr) { + LOG_ERROR("Secure shared capture CreateCompatibleDC failed, error={}", + GetLastError()); + ReleaseDC(nullptr, screen_dc); + return; + } + + BITMAPINFO bmi{}; + bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER); + bmi.bmiHeader.biWidth = request.width; + bmi.bmiHeader.biHeight = -request.height; + bmi.bmiHeader.biPlanes = 1; + bmi.bmiHeader.biBitCount = 32; + bmi.bmiHeader.biCompression = BI_RGB; + + void* bits = nullptr; + HBITMAP dib = + CreateDIBSection(mem_dc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0); + if (dib == nullptr || bits == nullptr) { + LOG_ERROR("Secure shared capture CreateDIBSection failed, error={}", + GetLastError()); + DeleteDC(mem_dc); + ReleaseDC(nullptr, screen_dc); + return; + } + + HGDIOBJ old_bitmap = SelectObject(mem_dc, dib); + while (!capture_state->stop_requested.load(std::memory_order_relaxed)) { + const auto frame_started = std::chrono::steady_clock::now(); + if (BitBlt(mem_dc, 0, 0, request.width, request.height, screen_dc, + request.left, request.top, SRCCOPY | CAPTUREBLT)) { + if (request.show_cursor) { + CURSORINFO cursor_info{}; + cursor_info.cbSize = sizeof(CURSORINFO); + if (GetCursorInfo(&cursor_info) && + cursor_info.flags == CURSOR_SHOWING && + cursor_info.hCursor != nullptr) { + const int cursor_x = cursor_info.ptScreenPos.x - request.left; + const int cursor_y = cursor_info.ptScreenPos.y - request.top; + if (cursor_x >= -64 && cursor_y >= -64 && + cursor_x < request.width + 64 && + cursor_y < request.height + 64) { + DrawIconEx(mem_dc, cursor_x, cursor_y, cursor_info.hCursor, 0, 0, + 0, nullptr, DI_NORMAL); + } + } + } + + const int convert_result = libyuv::ARGBToNV12( + static_cast(bits), request.width * 4, + nv12_frame.data(), request.width, + nv12_frame.data() + request.width * request.height, request.width, + request.width, request.height); + if (convert_result == 0) { + auto* header = + reinterpret_cast( + frame_view); + uint8_t* payload = frame_view + sizeof(*header); + header->writing = 1; + MemoryBarrier(); + header->magic = crossdesk::kCrossDeskSecureDesktopFrameMagic; + header->version = crossdesk::kCrossDeskSecureDesktopFrameVersion; + header->left = request.left; + header->top = request.top; + header->width = static_cast(request.width); + header->height = static_cast(request.height); + header->payload_size = static_cast(nv12_frame.size()); + std::memcpy(payload, nv12_frame.data(), nv12_frame.size()); + header->sequence = ++capture_state->sequence; + MemoryBarrier(); + header->writing = 0; + SetEvent(capture_state->frame_ready_event); + } + } else { + LOG_WARN("Secure shared capture BitBlt failed, error={}", GetLastError()); + } + + const auto elapsed_ms = + std::chrono::duration_cast( + std::chrono::steady_clock::now() - frame_started) + .count(); + if (elapsed_ms < interval_ms) { + std::this_thread::sleep_for( + std::chrono::milliseconds(interval_ms - elapsed_ms)); + } + } + + SelectObject(mem_dc, old_bitmap); + DeleteObject(dib); + DeleteDC(mem_dc); + ReleaseDC(nullptr, screen_dc); +} + +std::vector StopSecureDesktopSharedCapture( + SecureSharedCaptureState* capture_state) { + if (capture_state == nullptr) { + return BuildTextResponseBytes(BuildErrorJson("invalid_capture_state")); + } + + std::thread thread_to_join; + { + std::lock_guard lock(capture_state->mutex); + capture_state->stop_requested.store(true, std::memory_order_relaxed); + if (capture_state->frame_ready_event != nullptr) { + SetEvent(capture_state->frame_ready_event); + } + if (capture_state->capture_thread.joinable()) { + thread_to_join = std::move(capture_state->capture_thread); + } + } + + if (thread_to_join.joinable()) { + thread_to_join.join(); + } + + { + std::lock_guard lock(capture_state->mutex); + CloseSecureDesktopSharedCaptureResourcesLocked(capture_state); + capture_state->stop_requested.store(false, std::memory_order_relaxed); + capture_state->sequence = 0; + } + + return BuildTextResponseBytes("{\"ok\":true,\"shared_capture\":\"stopped\"}"); +} + +std::vector StartSecureDesktopSharedCapture( + const SecureCaptureRequest& request, + SecureSharedCaptureState* capture_state) { + if (capture_state == nullptr) { + return BuildTextResponseBytes(BuildErrorJson("invalid_capture_state")); + } + + StopSecureDesktopSharedCapture(capture_state); + + const size_t payload_size = + static_cast(request.width) * request.height * 3 / 2; + const size_t mapping_size = + sizeof(crossdesk::CrossDeskSecureDesktopSharedFrameHeader) + payload_size; + if (payload_size == 0 || mapping_size > MAXDWORD) { + return BuildTextResponseBytes(BuildErrorJson("invalid_capture_size")); + } + + PipeSecurityAttributes security_attributes; + SECURITY_ATTRIBUTES* attributes = nullptr; + if (security_attributes.Initialize()) { + attributes = security_attributes.get(); + } + + const std::wstring mapping_name = + crossdesk::GetCrossDeskSecureDesktopFrameMappingName( + capture_state->session_id); + const std::wstring event_name = + crossdesk::GetCrossDeskSecureDesktopFrameReadyEventName( + capture_state->session_id); + + HANDLE frame_mapping = + CreateFileMappingW(INVALID_HANDLE_VALUE, attributes, PAGE_READWRITE, 0, + static_cast(mapping_size), + mapping_name.c_str()); + if (frame_mapping == nullptr) { + return BuildTextResponseBytes( + BuildErrorJson("create_frame_mapping_failed", GetLastError())); + } + + auto* frame_view = static_cast( + MapViewOfFile(frame_mapping, FILE_MAP_ALL_ACCESS, 0, 0, mapping_size)); + if (frame_view == nullptr) { + const DWORD error = GetLastError(); + CloseHandle(frame_mapping); + return BuildTextResponseBytes(BuildErrorJson("map_frame_view_failed", + error)); + } + + HANDLE frame_ready_event = + CreateEventW(attributes, FALSE, FALSE, event_name.c_str()); + if (frame_ready_event == nullptr) { + const DWORD error = GetLastError(); + UnmapViewOfFile(frame_view); + CloseHandle(frame_mapping); + return BuildTextResponseBytes( + BuildErrorJson("create_frame_event_failed", error)); + } + + std::memset(frame_view, 0, mapping_size); + auto* header = + reinterpret_cast( + frame_view); + header->magic = crossdesk::kCrossDeskSecureDesktopFrameMagic; + header->version = crossdesk::kCrossDeskSecureDesktopFrameVersion; + header->left = request.left; + header->top = request.top; + header->width = static_cast(request.width); + header->height = static_cast(request.height); + header->buffer_size = static_cast(payload_size); + + { + std::lock_guard lock(capture_state->mutex); + capture_state->request = request; + capture_state->frame_mapping = frame_mapping; + capture_state->frame_ready_event = frame_ready_event; + capture_state->frame_view = frame_view; + capture_state->frame_view_size = mapping_size; + capture_state->sequence = 0; + capture_state->stop_requested.store(false, std::memory_order_relaxed); + capture_state->capture_thread = + std::thread(SecureDesktopSharedCaptureThread, capture_state); + } + + Json json; + json["ok"] = true; + json["shared_capture"] = "started"; + json["width"] = request.width; + json["height"] = request.height; + json["fps"] = request.fps; + return BuildTextResponseBytes(json.dump()); +} + std::vector HandleSecureInputHelperCommand( - const std::string& command, SecureCaptureBuffers* capture_buffers) { + const std::string& command, SecureCaptureBuffers* capture_buffers, + SecureSharedCaptureState* capture_state) { if (command == "ping") { return BuildTextResponseBytes("{\"ok\":true,\"reply\":\"pong\"}"); } + if (command == crossdesk::kCrossDeskSecureInputCaptureStopCommand) { + return StopSecureDesktopSharedCapture(capture_state); + } + + SecureCaptureRequest capture_start_request; + if (ParseSecureInputCaptureStartCommand(command, &capture_start_request)) { + return StartSecureDesktopSharedCapture(capture_start_request, + capture_state); + } + int key_code = 0; bool is_down = false; uint32_t scan_code = 0; @@ -940,14 +1280,17 @@ std::vector HandleSecureInputHelperCommand( return BuildTextResponseBytes(BuildErrorJson("unknown_command")); } -void HandleSecureInputHelperPipeClient(HANDLE pipe, HANDLE event_handle) { +void HandleSecureInputHelperPipeClient( + HANDLE pipe, HANDLE event_handle, + std::shared_ptr capture_state) { SecureCaptureBuffers capture_buffers; char buffer[1024] = {0}; DWORD bytes_read = 0; if (ReadFile(pipe, buffer, sizeof(buffer) - 1, &bytes_read, nullptr) && bytes_read > 0) { std::vector response = HandleSecureInputHelperCommand( - std::string(buffer, buffer + bytes_read), &capture_buffers); + std::string(buffer, buffer + bytes_read), &capture_buffers, + capture_state.get()); DWORD bytes_written = 0; if (!response.empty()) { WriteFile(pipe, response.data(), static_cast(response.size()), @@ -964,6 +1307,9 @@ void HandleSecureInputHelperPipeClient(HANDLE pipe, HANDLE event_handle) { } void SecureInputHelperIpcServerLoop(HANDLE stop_event, DWORD session_id) { + auto capture_state = std::make_shared(); + capture_state->session_id = session_id; + PipeSecurityAttributes security_attributes; SECURITY_ATTRIBUTES* pipe_attributes = nullptr; if (security_attributes.Initialize()) { @@ -1029,9 +1375,12 @@ void SecureInputHelperIpcServerLoop(HANDLE stop_event, DWORD session_id) { } } - std::thread(HandleSecureInputHelperPipeClient, pipe, overlapped.hEvent) + std::thread(HandleSecureInputHelperPipeClient, pipe, overlapped.hEvent, + capture_state) .detach(); } + + StopSecureDesktopSharedCapture(capture_state.get()); } void PrintUsage() { @@ -1206,4 +1555,4 @@ int main(int argc, char* argv[]) { LOG_INFO("Session helper exiting: session_id={}", current_session_id); return 0; -} \ No newline at end of file +} diff --git a/src/service/windows/session_helper_shared.h b/src/service/windows/session_helper_shared.h index 1dabd24..09b6659 100644 --- a/src/service/windows/session_helper_shared.h +++ b/src/service/windows/session_helper_shared.h @@ -23,7 +23,15 @@ inline constexpr char kCrossDeskSecureInputKeyboardCommandPrefix[] = "keyboard:"; inline constexpr char kCrossDeskSecureInputMouseCommandPrefix[] = "mouse:"; inline constexpr char kCrossDeskSecureInputCaptureCommandPrefix[] = "capture:"; +inline constexpr char kCrossDeskSecureInputCaptureStartCommandPrefix[] = + "capture-start:"; +inline constexpr char kCrossDeskSecureInputCaptureStopCommand[] = + "capture-stop"; inline constexpr DWORD kCrossDeskSecureInputPipeBufferBytes = 16 * 1024 * 1024; +inline constexpr wchar_t kCrossDeskSecureDesktopFrameMappingPrefix[] = + L"Global\\CrossDeskSecureDesktopFrame-"; +inline constexpr wchar_t kCrossDeskSecureDesktopFrameReadyEventPrefix[] = + L"Global\\CrossDeskSecureDesktopFrameReady-"; inline constexpr uint32_t kCrossDeskSecureDesktopFrameMagic = 0x50444358; inline constexpr uint32_t kCrossDeskSecureDesktopFrameVersion = 1; @@ -37,6 +45,19 @@ struct CrossDeskSecureDesktopFrameHeader { uint32_t height; uint32_t payload_size; }; + +struct CrossDeskSecureDesktopSharedFrameHeader { + uint32_t magic; + uint32_t version; + volatile uint32_t writing; + uint32_t sequence; + int32_t left; + int32_t top; + uint32_t width; + uint32_t height; + uint32_t payload_size; + uint32_t buffer_size; +}; #pragma pack(pop) inline std::wstring GetCrossDeskSessionHelperPipeName(DWORD session_id) { @@ -49,6 +70,18 @@ inline std::wstring GetCrossDeskSecureInputHelperPipeName(DWORD session_id) { std::to_wstring(session_id); } +inline std::wstring GetCrossDeskSecureDesktopFrameMappingName( + DWORD session_id) { + return std::wstring(kCrossDeskSecureDesktopFrameMappingPrefix) + + std::to_wstring(session_id); +} + +inline std::wstring GetCrossDeskSecureDesktopFrameReadyEventName( + DWORD session_id) { + return std::wstring(kCrossDeskSecureDesktopFrameReadyEventPrefix) + + std::to_wstring(session_id); +} + } // namespace crossdesk -#endif \ No newline at end of file +#endif