mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-31 02:49:35 +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 start_screen_capturer_ = false;
|
||||||
bool screen_capturer_is_started_ = false;
|
bool screen_capturer_is_started_ = false;
|
||||||
bool start_keyboard_capturer_ = false;
|
bool start_keyboard_capturer_ = false;
|
||||||
bool show_cursor_ = false;
|
|
||||||
bool keyboard_capturer_is_started_ = false;
|
bool keyboard_capturer_is_started_ = false;
|
||||||
bool keyboard_capturer_uses_window_events_ = false;
|
bool keyboard_capturer_uses_window_events_ = false;
|
||||||
bool foucs_on_main_window_ = false;
|
bool foucs_on_main_window_ = false;
|
||||||
|
|||||||
@@ -271,6 +271,15 @@ int SessionDeviceManager::InitializeScreenCapturer() {
|
|||||||
return -1;
|
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() {
|
int SessionDeviceManager::StartScreenCapturer() {
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
if (!owner_.EnsureMacScreenRecordingPermission()) {
|
if (!owner_.EnsureMacScreenRecordingPermission()) {
|
||||||
@@ -286,8 +295,11 @@ int SessionDeviceManager::StartScreenCapturer() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
LOG_INFO("Start screen capturer, show cursor: {}", owner_.show_cursor_);
|
// Clear before sampling, so a change arriving after the sample remains pending.
|
||||||
const int ret = screen_capturer_->Start(owner_.show_cursor_);
|
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) {
|
if (ret != 0) {
|
||||||
LOG_ERROR("Start screen capturer failed: {}", ret);
|
LOG_ERROR("Start screen capturer failed: {}", ret);
|
||||||
}
|
}
|
||||||
@@ -503,6 +515,15 @@ void SessionDeviceManager::PushAudio(const char *data, size_t size) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void SessionDeviceManager::UpdateInteractions() {
|
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__)
|
#if defined(__linux__) && !defined(__APPLE__)
|
||||||
const bool is_wayland_session = IsWaylandSession();
|
const bool is_wayland_session = IsWaylandSession();
|
||||||
const bool stop_wayland_mouse_before_screen =
|
const bool stop_wayland_mouse_before_screen =
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
#include <SDL3/SDL.h>
|
#include <SDL3/SDL.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cstddef>
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
@@ -40,6 +41,11 @@ public:
|
|||||||
|
|
||||||
void Initialize();
|
void Initialize();
|
||||||
void UpdateInteractions();
|
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 DestroyDevices();
|
||||||
void DestroyFactories();
|
void DestroyFactories();
|
||||||
|
|
||||||
@@ -84,6 +90,7 @@ private:
|
|||||||
void ClearCapturedKeyboardInput();
|
void ClearCapturedKeyboardInput();
|
||||||
bool ShouldSendCapturedFrame(std::chrono::steady_clock::time_point now,
|
bool ShouldSendCapturedFrame(std::chrono::steady_clock::time_point now,
|
||||||
int fps);
|
int fps);
|
||||||
|
bool ShouldCaptureCursor() const;
|
||||||
void RecordCaptureCadence(std::chrono::steady_clock::time_point now, int fps,
|
void RecordCaptureCadence(std::chrono::steady_clock::time_point now, int fps,
|
||||||
bool from_secure_desktop);
|
bool from_secure_desktop);
|
||||||
|
|
||||||
@@ -91,6 +98,7 @@ private:
|
|||||||
SDL_AudioStream *output_stream_ = nullptr;
|
SDL_AudioStream *output_stream_ = nullptr;
|
||||||
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
|
ScreenCapturerFactory *screen_capturer_factory_ = nullptr;
|
||||||
ScreenCapturer *screen_capturer_ = nullptr;
|
ScreenCapturer *screen_capturer_ = nullptr;
|
||||||
|
std::atomic<bool> cursor_capture_dirty_{true};
|
||||||
SpeakerCaptureController speaker_capture_;
|
SpeakerCaptureController speaker_capture_;
|
||||||
DeviceControllerFactory *device_controller_factory_ = nullptr;
|
DeviceControllerFactory *device_controller_factory_ = nullptr;
|
||||||
MouseController *mouse_controller_ = nullptr;
|
MouseController *mouse_controller_ = nullptr;
|
||||||
|
|||||||
@@ -140,11 +140,11 @@ void GuiRuntime::HandleServerControllerDisconnected(
|
|||||||
}
|
}
|
||||||
|
|
||||||
bool has_connected_controller = false;
|
bool has_connected_controller = false;
|
||||||
bool has_web_controller = false;
|
|
||||||
std::string remaining_controller_id;
|
std::string remaining_controller_id;
|
||||||
{
|
{
|
||||||
std::unique_lock lock(connection_status_mutex_);
|
std::unique_lock lock(connection_status_mutex_);
|
||||||
connection_status_.erase(remote_id);
|
connection_status_.erase(remote_id);
|
||||||
|
devices_.OnControllerConnectionsChanged();
|
||||||
connection_host_names_.erase(remote_id);
|
connection_host_names_.erase(remote_id);
|
||||||
privacy_sessions_.Disconnected(remote_id);
|
privacy_sessions_.Disconnected(remote_id);
|
||||||
for (const auto& [id, status] : connection_status_) {
|
for (const auto& [id, status] : connection_status_) {
|
||||||
@@ -152,14 +152,11 @@ void GuiRuntime::HandleServerControllerDisconnected(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
has_connected_controller = true;
|
has_connected_controller = true;
|
||||||
has_web_controller =
|
|
||||||
has_web_controller || id.find("web") != std::string::npos;
|
|
||||||
if (remaining_controller_id.empty()) {
|
if (remaining_controller_id.empty()) {
|
||||||
remaining_controller_id = id;
|
remaining_controller_id = id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
show_cursor_ = has_web_controller;
|
|
||||||
if (has_connected_controller) {
|
if (has_connected_controller) {
|
||||||
remote_client_id_ = remaining_controller_id;
|
remote_client_id_ = remaining_controller_id;
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
|
|
||||||
#include "platform/windows/input/windows_cursor_state.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/dxgi_cursor_state.h"
|
||||||
#include "platform/windows/screen_capturer/secure_desktop_cursor_state.h"
|
#include "platform/windows/screen_capturer/secure_desktop_cursor_state.h"
|
||||||
#include "runtime/cursor_position.h"
|
#include "runtime/cursor_position.h"
|
||||||
@@ -53,8 +54,9 @@ bool CursorStateProvider::Sample(const std::vector<DisplayInfo>& displays,
|
|||||||
void* cursor_monitor = state->position_valid
|
void* cursor_monitor = state->position_valid
|
||||||
? displays[state->display_id].handle
|
? displays[state->display_id].handle
|
||||||
: nullptr;
|
: nullptr;
|
||||||
const bool embedded = (info.flags & CURSOR_SHOWING) != 0 &&
|
const bool embedded = SharedCapturedCursorState().IsEmbedded(cursor_monitor) ||
|
||||||
!SharedDxgiCursorState().ShouldDrawCursor(true, cursor_monitor);
|
((info.flags & CURSOR_SHOWING) != 0 &&
|
||||||
|
!SharedDxgiCursorState().ShouldDrawCursor(true, cursor_monitor));
|
||||||
impl_->cursor.Sample(info, embedded, state);
|
impl_->cursor.Sample(info, embedded, state);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -419,6 +419,7 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
runtime->connection_status_[remote_id] = status;
|
runtime->connection_status_[remote_id] = status;
|
||||||
|
runtime->devices_.OnControllerConnectionsChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (status) {
|
switch (status) {
|
||||||
@@ -485,16 +486,6 @@ void PeerEventHandler::OnConnectionStatus(ConnectionStatus status,
|
|||||||
runtime->devices_.StartSpeakerCapturer();
|
runtime->devices_.StartSpeakerCapturer();
|
||||||
runtime->remote_client_id_ = remote_id;
|
runtime->remote_client_id_ = remote_id;
|
||||||
runtime->start_mouse_controller_ = true;
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,14 +24,14 @@ class WindowsCursorState {
|
|||||||
public:
|
public:
|
||||||
void Sample(const CURSORINFO& info, bool embedded, CursorState* state) {
|
void Sample(const CURSORINFO& info, bool embedded, CursorState* state) {
|
||||||
const bool showing = (info.flags & CURSOR_SHOWING) != 0;
|
const bool showing = (info.flags & CURSOR_SHOWING) != 0;
|
||||||
state->render_mode = showing ? (embedded ? CursorRenderMode::embedded
|
state->render_mode = embedded ? CursorRenderMode::embedded
|
||||||
: CursorRenderMode::separate)
|
: showing ? CursorRenderMode::separate
|
||||||
: CursorRenderMode::hidden;
|
: CursorRenderMode::hidden;
|
||||||
state->visible = state->render_mode == CursorRenderMode::separate;
|
state->visible = state->render_mode == CursorRenderMode::separate;
|
||||||
state->shape = state->visible ? ShapeFromWindowsCursor(info.hCursor)
|
state->shape = state->visible ? ShapeFromWindowsCursor(info.hCursor)
|
||||||
: RemoteCursorShape::none;
|
: RemoteCursorShape::none;
|
||||||
state->hidden_reason = CursorHiddenReason::unspecified;
|
state->hidden_reason = CursorHiddenReason::unspecified;
|
||||||
if (!showing) {
|
if (!showing && !embedded) {
|
||||||
state->hidden_reason = (info.flags & CURSOR_SUPPRESSED)
|
state->hidden_reason = (info.flags & CURSOR_SUPPRESSED)
|
||||||
? CursorHiddenReason::system_suppressed
|
? CursorHiddenReason::system_suppressed
|
||||||
: info.hCursor && NoPointingDevice()
|
: info.hCursor && NoPointingDevice()
|
||||||
@@ -83,4 +83,4 @@ class WindowsCursorState {
|
|||||||
|
|
||||||
} // namespace crossdesk
|
} // 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 <vector>
|
||||||
|
|
||||||
#include <display_stream_id.h>
|
#include <display_stream_id.h>
|
||||||
|
#include "captured_cursor_state.h"
|
||||||
|
#include "cursor_draw.h"
|
||||||
#include "libyuv.h"
|
#include "libyuv.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
|
||||||
@@ -184,18 +186,15 @@ void ScreenCapturerGdi::CaptureLoop() {
|
|||||||
BitBlt(mem_dc, 0, 0, width, height, screen_dc, left, top,
|
BitBlt(mem_dc, 0, 0, width, height, screen_dc, left, top,
|
||||||
SRCCOPY | CAPTUREBLT);
|
SRCCOPY | CAPTUREBLT);
|
||||||
|
|
||||||
|
bool cursor_embedded = false;
|
||||||
if (show_cursor_) {
|
if (show_cursor_) {
|
||||||
CURSORINFO ci{};
|
CURSORINFO ci{};
|
||||||
ci.cbSize = sizeof(CURSORINFO);
|
ci.cbSize = sizeof(CURSORINFO);
|
||||||
if (GetCursorInfo(&ci) && ci.flags == CURSOR_SHOWING && ci.hCursor) {
|
if (GetCursorInfo(&ci)) {
|
||||||
POINT pt = ci.ptScreenPos;
|
cursor_embedded = DrawCursorInCapture(mem_dc, ci, left, top, width, height);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
GdiFlush();
|
||||||
|
|
||||||
int stride_argb = width * 4;
|
int stride_argb = width * 4;
|
||||||
int nv12_size = width * height * 3 / 2;
|
int nv12_size = width * height * 3 / 2;
|
||||||
@@ -211,6 +210,7 @@ void ScreenCapturerGdi::CaptureLoop() {
|
|||||||
width, height);
|
width, height);
|
||||||
|
|
||||||
if (callback_) {
|
if (callback_) {
|
||||||
|
CapturedCursorFrameScope cursor_scope(cursor_embedded);
|
||||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||||
callback_(nv12_frame_, nv12_size, width, height, stream_id.c_str(),
|
callback_(nv12_frame_, nv12_size, width, height, stream_id.c_str(),
|
||||||
nullptr);
|
nullptr);
|
||||||
|
|||||||
@@ -31,6 +31,9 @@ class ScreenCapturerGdi : public ScreenCapturer {
|
|||||||
int Destroy() override;
|
int Destroy() override;
|
||||||
int Start(bool show_cursor) override;
|
int Start(bool show_cursor) override;
|
||||||
int Stop() override;
|
int Stop() override;
|
||||||
|
void SetCursorCapture(bool enabled) {
|
||||||
|
show_cursor_.store(enabled, std::memory_order_relaxed);
|
||||||
|
}
|
||||||
|
|
||||||
int Pause(int monitor_index) override;
|
int Pause(int monitor_index) override;
|
||||||
int Resume(int monitor_index) override;
|
int Resume(int monitor_index) override;
|
||||||
@@ -65,4 +68,4 @@ class ScreenCapturerGdi : public ScreenCapturer {
|
|||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -8,6 +8,7 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
|
||||||
#include <display_stream_id.h>
|
#include <display_stream_id.h>
|
||||||
|
#include "captured_cursor_state.h"
|
||||||
#include "libyuv.h"
|
#include "libyuv.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
|
||||||
@@ -72,6 +73,16 @@ HMONITOR GetPrimaryMonitor() {
|
|||||||
|
|
||||||
ScreenCapturerWgc::ScreenCapturerWgc() : monitor_(nullptr) {}
|
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() {
|
ScreenCapturerWgc::~ScreenCapturerWgc() {
|
||||||
Stop();
|
Stop();
|
||||||
CleanUp();
|
CleanUp();
|
||||||
@@ -409,6 +420,7 @@ void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
|||||||
even_width, even_width, even_height);
|
even_width, even_width, even_height);
|
||||||
|
|
||||||
const std::string stream_id = MakeDisplayStreamId(id);
|
const std::string stream_id = MakeDisplayStreamId(id);
|
||||||
|
CapturedCursorFrameScope cursor_scope(frame.cursor_captured);
|
||||||
on_data_(nv12_frame_, nv12_size, even_width, even_height,
|
on_data_(nv12_frame_, nv12_size, even_width, even_height,
|
||||||
stream_id.c_str(), nullptr);
|
stream_id.c_str(), nullptr);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
|||||||
int Destroy() override;
|
int Destroy() override;
|
||||||
int Start(bool show_cursor) override;
|
int Start(bool show_cursor) override;
|
||||||
int Stop() override;
|
int Stop() override;
|
||||||
|
int SetCursorCapture(bool enabled);
|
||||||
|
|
||||||
int Pause(int monitor_index) override;
|
int Pause(int monitor_index) override;
|
||||||
int Resume(int monitor_index) override;
|
int Resume(int monitor_index) override;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@
|
|||||||
|
|
||||||
#include <display_stream_id.h>
|
#include <display_stream_id.h>
|
||||||
#include "captured_nv12_frame.h"
|
#include "captured_nv12_frame.h"
|
||||||
|
#include "captured_cursor_state.h"
|
||||||
#include "dxgi_cursor_state.h"
|
#include "dxgi_cursor_state.h"
|
||||||
#include "interactive_state.h"
|
#include "interactive_state.h"
|
||||||
#include "named_pipe_deadline.h"
|
#include "named_pipe_deadline.h"
|
||||||
@@ -52,6 +53,8 @@ class WgcPluginCapturer final : public ScreenCapturer {
|
|||||||
public:
|
public:
|
||||||
using CreateFn = ScreenCapturer* (*)();
|
using CreateFn = ScreenCapturer* (*)();
|
||||||
using DestroyFn = void (*)(ScreenCapturer*);
|
using DestroyFn = void (*)(ScreenCapturer*);
|
||||||
|
using SetCursorFn = decltype(&CrossDeskSetWgcCursorCapture);
|
||||||
|
using FrameCursorFn = decltype(&CrossDeskWgcFrameCapturesCursor);
|
||||||
|
|
||||||
static std::unique_ptr<ScreenCapturer> Create() {
|
static std::unique_ptr<ScreenCapturer> Create() {
|
||||||
std::filesystem::path plugin_path;
|
std::filesystem::path plugin_path;
|
||||||
@@ -99,12 +102,38 @@ class WgcPluginCapturer final : public ScreenCapturer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int Init(const int fps, cb_desktop_data cb) override {
|
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 Destroy() override { return impl_ ? impl_->Destroy() : 0; }
|
||||||
int Start(bool show_cursor) override {
|
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;
|
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 Stop() override { return impl_ ? impl_->Stop() : 0; }
|
||||||
int Pause(int monitor_index) override {
|
int Pause(int monitor_index) override {
|
||||||
return impl_ ? impl_->Pause(monitor_index) : -1;
|
return impl_ ? impl_->Pause(monitor_index) : -1;
|
||||||
@@ -124,11 +153,21 @@ class WgcPluginCapturer final : public ScreenCapturer {
|
|||||||
|
|
||||||
private:
|
private:
|
||||||
WgcPluginCapturer(HMODULE module, ScreenCapturer* impl, DestroyFn destroy_fn)
|
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;
|
HMODULE module_ = nullptr;
|
||||||
ScreenCapturer* impl_ = nullptr;
|
ScreenCapturer* impl_ = nullptr;
|
||||||
DestroyFn destroy_fn_ = 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) {
|
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 char* reported_stream_id,
|
||||||
const MiniRtcNativeVideoFrame* native_frame) {
|
const MiniRtcNativeVideoFrame* native_frame) {
|
||||||
if (size == ScreenCapturer::kBackendReset) {
|
if (size == ScreenCapturer::kBackendReset) {
|
||||||
|
SharedCapturedCursorState().Reset();
|
||||||
if (privacy_) privacy_->Fail("Capture restarted; privacy screen will turn off");
|
if (privacy_) privacy_->Fail("Capture restarted; privacy screen will turn off");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) {
|
if (secure_desktop_capture_active_.load(std::memory_order_relaxed)) {
|
||||||
SharedDxgiCursorState().Reset();
|
SharedDxgiCursorState().Reset();
|
||||||
|
SharedCapturedCursorState().Reset();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* raw_stream_id = reported_stream_id ? reported_stream_id : "";
|
const char* raw_stream_id = reported_stream_id ? reported_stream_id : "";
|
||||||
std::string mapped_stream_id;
|
std::string mapped_stream_id;
|
||||||
|
void* frame_monitor = nullptr;
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(alias_mutex_);
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
auto it = stream_id_alias_.find(raw_stream_id);
|
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 = ResolveDisplayStreamId(
|
||||||
mapped_stream_id.c_str(), canonical_displays_.size(),
|
mapped_stream_id.c_str(), canonical_displays_.size(),
|
||||||
monitor_index_.load(std::memory_order_relaxed));
|
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()) {
|
if (privacy_ && privacy_->Engaged() && !IsWindowsPrivacyDesktopAvailable()) {
|
||||||
privacy_->Fail("Secure or unavailable desktop; privacy screen will turn off");
|
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;
|
return;
|
||||||
}
|
}
|
||||||
invalid_stream_id_logged_.store(false, std::memory_order_relaxed);
|
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(
|
if (post_secure_desktop_waiting_for_frame_.exchange(
|
||||||
false, std::memory_order_relaxed)) {
|
false, std::memory_order_relaxed)) {
|
||||||
const ULONGLONG start_tick =
|
const ULONGLONG start_tick =
|
||||||
@@ -476,7 +527,10 @@ void ScreenCapturerWin::EmitCapturedFrame(
|
|||||||
bool from_secure_desktop) {
|
bool from_secure_desktop) {
|
||||||
// Helper frames do not use DXGI's pointer plane. Do not carry normal-desktop
|
// Helper frames do not use DXGI's pointer plane. Do not carry normal-desktop
|
||||||
// cursor suppression into the lock screen or UAC 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
|
// Secure-desktop capture takes priority; remove privacy without dropping
|
||||||
// the helper frame or delaying the remote session.
|
// the helper frame or delaying the remote session.
|
||||||
if (from_secure_desktop && privacy_ && privacy_->Engaged()) {
|
if (from_secure_desktop && privacy_ && privacy_->Engaged()) {
|
||||||
@@ -543,9 +597,10 @@ bool ScreenCapturerWin::TryStartBackend(
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
const char* name = CaptureBackendName(candidate.get());
|
const char* name = CaptureBackendName(candidate.get());
|
||||||
|
const bool show_cursor = show_cursor_.load(std::memory_order_relaxed);
|
||||||
int ret = candidate->Init(fps_, cb_);
|
int ret = candidate->Init(fps_, cb_);
|
||||||
if (ret == 0)
|
if (ret == 0)
|
||||||
ret = candidate->Start(show_cursor_.load(std::memory_order_relaxed));
|
ret = candidate->Start(show_cursor);
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
LOG_WARN("Windows capturer: {} initialization/start failed (ret={})", name,
|
LOG_WARN("Windows capturer: {} initialization/start failed (ret={})", name,
|
||||||
ret);
|
ret);
|
||||||
@@ -555,6 +610,7 @@ bool ScreenCapturerWin::TryStartBackend(
|
|||||||
// Commit only after the replacement is running. Failed candidates release
|
// Commit only after the replacement is running. Failed candidates release
|
||||||
// their resources without replacing the current backend.
|
// their resources without replacing the current backend.
|
||||||
impl_ = std::move(candidate);
|
impl_ = std::move(candidate);
|
||||||
|
applied_show_cursor_ = show_cursor;
|
||||||
RestoreMonitor(monitor_index);
|
RestoreMonitor(monitor_index);
|
||||||
LOG_INFO("Windows capturer: started {}", name);
|
LOG_INFO("Windows capturer: started {}", name);
|
||||||
return true;
|
return true;
|
||||||
@@ -568,6 +624,8 @@ int ScreenCapturerWin::Start(bool show_cursor) {
|
|||||||
NotifyPrivacyCapture(false);
|
NotifyPrivacyCapture(false);
|
||||||
|
|
||||||
show_cursor_.store(show_cursor, std::memory_order_relaxed);
|
show_cursor_.store(show_cursor, std::memory_order_relaxed);
|
||||||
|
applied_show_cursor_ = show_cursor;
|
||||||
|
SharedCapturedCursorState().Reset();
|
||||||
paused_.store(false, std::memory_order_relaxed);
|
paused_.store(false, std::memory_order_relaxed);
|
||||||
invalid_stream_id_logged_.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_drop_logged_.store(false, std::memory_order_relaxed);
|
||||||
post_secure_desktop_started_tick_.store(0, std::memory_order_relaxed);
|
post_secure_desktop_started_tick_.store(0, std::memory_order_relaxed);
|
||||||
int ret = 0;
|
int ret = 0;
|
||||||
|
// Drain management operations before stopping/replacing their backend.
|
||||||
|
StopSecureCaptureThread();
|
||||||
if (impl_) {
|
if (impl_) {
|
||||||
ret = impl_->Stop();
|
ret = impl_->Stop();
|
||||||
}
|
}
|
||||||
StopSecureCaptureThread();
|
SharedCapturedCursorState().Reset();
|
||||||
SharedSecureDesktopCursorState().SetActive(false);
|
SharedSecureDesktopCursorState().SetActive(false);
|
||||||
StopSecureDesktopSharedCapture(secure_shared_session_id_);
|
StopSecureDesktopSharedCapture(secure_shared_session_id_);
|
||||||
return ret;
|
return ret;
|
||||||
@@ -798,6 +858,7 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
|
|||||||
impl_->Stop();
|
impl_->Stop();
|
||||||
int ret = impl_->Start(show_cursor);
|
int ret = impl_->Start(show_cursor);
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
applied_show_cursor_ = show_cursor;
|
||||||
RestoreMonitor(current_monitor);
|
RestoreMonitor(current_monitor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -812,6 +873,7 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
|
|||||||
ret = impl_->Start(show_cursor);
|
ret = impl_->Start(show_cursor);
|
||||||
}
|
}
|
||||||
if (ret == 0) {
|
if (ret == 0) {
|
||||||
|
applied_show_cursor_ = show_cursor;
|
||||||
RestoreMonitor(current_monitor);
|
RestoreMonitor(current_monitor);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -834,6 +896,24 @@ bool ScreenCapturerWin::RestartCaptureBackendAfterSecureDesktop() {
|
|||||||
return false;
|
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,
|
bool ScreenCapturerWin::GetCurrentCaptureRegion(int* left, int* top, int* width,
|
||||||
int* height,
|
int* height,
|
||||||
std::string* display_name) {
|
std::string* display_name) {
|
||||||
@@ -1113,6 +1193,7 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
|
|||||||
bool post_secure_restart_pending = false;
|
bool post_secure_restart_pending = false;
|
||||||
ULONGLONG post_secure_restart_deadline_tick = 0;
|
ULONGLONG post_secure_restart_deadline_tick = 0;
|
||||||
ULONGLONG last_post_secure_restart_tick = 0;
|
ULONGLONG last_post_secure_restart_tick = 0;
|
||||||
|
ULONGLONG next_cursor_update_tick = 0;
|
||||||
SecureDesktopServiceStatus status;
|
SecureDesktopServiceStatus status;
|
||||||
SecureDesktopStatusPoller status_poller(
|
SecureDesktopStatusPoller status_poller(
|
||||||
QuerySecureDesktopServiceStatus,
|
QuerySecureDesktopServiceStatus,
|
||||||
@@ -1250,6 +1331,10 @@ void ScreenCapturerWin::SecureDesktopCaptureLoop() {
|
|||||||
!RestartCaptureBackendAfterSecureDesktop();
|
!RestartCaptureBackendAfterSecureDesktop();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (!post_secure_restart_pending && now >= next_cursor_update_tick) {
|
||||||
|
next_cursor_update_tick = now + 500;
|
||||||
|
ApplyCursorCaptureSetting();
|
||||||
|
}
|
||||||
std::this_thread::sleep_for(
|
std::this_thread::sleep_for(
|
||||||
std::chrono::milliseconds(status.service_available ? 50 : 200));
|
std::chrono::milliseconds(status.service_available ? 50 : 200));
|
||||||
continue;
|
continue;
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ class ScreenCapturerWin : public ScreenCapturer {
|
|||||||
int Destroy() override;
|
int Destroy() override;
|
||||||
int Start(bool show_cursor) override;
|
int Start(bool show_cursor) override;
|
||||||
int Stop() 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 Pause(int monitor_index) override;
|
||||||
int Resume(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> running_{false};
|
||||||
std::atomic<bool> paused_{false};
|
std::atomic<bool> paused_{false};
|
||||||
std::atomic<bool> show_cursor_{true};
|
std::atomic<bool> show_cursor_{true};
|
||||||
|
bool applied_show_cursor_ = true;
|
||||||
std::atomic<int> monitor_index_{0};
|
std::atomic<int> monitor_index_{0};
|
||||||
int initial_monitor_index_ = 0;
|
int initial_monitor_index_ = 0;
|
||||||
std::atomic<bool> secure_desktop_capture_active_{false};
|
std::atomic<bool> secure_desktop_capture_active_{false};
|
||||||
@@ -109,6 +114,7 @@ class ScreenCapturerWin : public ScreenCapturer {
|
|||||||
bool from_secure_desktop = false);
|
bool from_secure_desktop = false);
|
||||||
void StopSecureCaptureThread();
|
void StopSecureCaptureThread();
|
||||||
bool RestartCaptureBackendAfterSecureDesktop();
|
bool RestartCaptureBackendAfterSecureDesktop();
|
||||||
|
void ApplyCursorCaptureSetting();
|
||||||
void SecureDesktopCaptureLoop();
|
void SecureDesktopCaptureLoop();
|
||||||
bool GetCurrentCaptureRegion(int* left, int* top, int* width, int* height,
|
bool GetCurrentCaptureRegion(int* left, int* top, int* width, int* height,
|
||||||
std::string* display_name);
|
std::string* display_name);
|
||||||
|
|||||||
@@ -24,6 +24,10 @@ CROSSDESK_WGC_PLUGIN_API crossdesk::ScreenCapturer*
|
|||||||
CrossDeskCreateWgcCapturer();
|
CrossDeskCreateWgcCapturer();
|
||||||
CROSSDESK_WGC_PLUGIN_API void CrossDeskDestroyWgcCapturer(
|
CROSSDESK_WGC_PLUGIN_API void CrossDeskDestroyWgcCapturer(
|
||||||
crossdesk::ScreenCapturer* capturer);
|
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 <mutex>
|
||||||
|
|
||||||
|
#include "captured_cursor_state.h"
|
||||||
#include "path_manager.h"
|
#include "path_manager.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "screen_capturer_wgc.h"
|
#include "screen_capturer_wgc.h"
|
||||||
@@ -27,4 +28,12 @@ crossdesk::ScreenCapturer* CrossDeskCreateWgcCapturer() {
|
|||||||
void CrossDeskDestroyWgcCapturer(crossdesk::ScreenCapturer* capturer) {
|
void CrossDeskDestroyWgcCapturer(crossdesk::ScreenCapturer* capturer) {
|
||||||
delete 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;
|
unsigned int row_pitch;
|
||||||
|
|
||||||
const unsigned char* data;
|
const unsigned char* data;
|
||||||
|
bool cursor_captured;
|
||||||
};
|
};
|
||||||
|
|
||||||
class wgc_session_observer {
|
class wgc_session_observer {
|
||||||
@@ -38,4 +39,4 @@ class WgcSession {
|
|||||||
virtual ~WgcSession() {};
|
virtual ~WgcSession() {};
|
||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -201,7 +201,13 @@ int WgcSessionImpl::StartCaptureLocked(bool show_cursor) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::StartLocked(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;
|
last_show_cursor_ = show_cursor;
|
||||||
if (!is_initialized_) {
|
if (!is_initialized_) {
|
||||||
@@ -218,6 +224,7 @@ int WgcSessionImpl::StartLocked(bool show_cursor) {
|
|||||||
|
|
||||||
LOG_WARN("WGC: start capture failed, rebuilding capture item");
|
LOG_WARN("WGC: start capture failed, rebuilding capture item");
|
||||||
CleanUpLocked();
|
CleanUpLocked();
|
||||||
|
is_paused_ = was_paused;
|
||||||
ret = InitializeLocked();
|
ret = InitializeLocked();
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
return ret;
|
return ret;
|
||||||
@@ -259,10 +266,12 @@ void WgcSessionImpl::OnFrame(
|
|||||||
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
|
[[maybe_unused]] winrt::Windows::Foundation::IInspectable const& args) {
|
||||||
std::lock_guard locker(lock_);
|
std::lock_guard locker(lock_);
|
||||||
|
|
||||||
|
if (!is_running_ || sender != capture_framepool_) return;
|
||||||
auto is_new_size = false;
|
auto is_new_size = false;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
auto frame = sender.TryGetNextFrame();
|
auto frame = sender.TryGetNextFrame();
|
||||||
|
if (!frame) return;
|
||||||
auto frame_size = frame.ContentSize();
|
auto frame_size = frame.ContentSize();
|
||||||
|
|
||||||
if (frame_size.Width != capture_frame_size_.Width ||
|
if (frame_size.Width != capture_frame_size_.Width ||
|
||||||
@@ -310,12 +319,21 @@ void WgcSessionImpl::OnFrame(
|
|||||||
|
|
||||||
// copy data from map_result.pData
|
// copy data from map_result.pData
|
||||||
if (map_result.pData && observer_) {
|
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(
|
observer_->OnFrame(
|
||||||
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
|
wgc_session_frame{static_cast<unsigned int>(frame_size.Width),
|
||||||
static_cast<unsigned int>(frame_size.Height),
|
static_cast<unsigned int>(frame_size.Height),
|
||||||
map_result.RowPitch,
|
map_result.RowPitch,
|
||||||
const_cast<const unsigned char*>(
|
const_cast<const unsigned char*>(
|
||||||
(unsigned char*)map_result.pData)},
|
(unsigned char*)map_result.pData),
|
||||||
|
cursor_captured},
|
||||||
id_);
|
id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -26,6 +26,7 @@
|
|||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "session_helper_shared.h"
|
#include "session_helper_shared.h"
|
||||||
#include "windows_cursor_state.h"
|
#include "windows_cursor_state.h"
|
||||||
|
#include "cursor_draw.h"
|
||||||
#include "windows_input_marker.h"
|
#include "windows_input_marker.h"
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
@@ -1201,21 +1202,12 @@ struct SecureDesktopGdiResources {
|
|||||||
cursor_snapshot.hidden_reason = static_cast<uint32_t>(state.hidden_reason);
|
cursor_snapshot.hidden_reason = static_cast<uint32_t>(state.hidden_reason);
|
||||||
cursor_snapshot.x = cursor.ptScreenPos.x;
|
cursor_snapshot.x = cursor.ptScreenPos.x;
|
||||||
cursor_snapshot.y = cursor.ptScreenPos.y;
|
cursor_snapshot.y = cursor.ptScreenPos.y;
|
||||||
if (request.show_cursor && cursor_snapshot.visible &&
|
if (request.show_cursor && crossdesk::DrawCursorInCapture(
|
||||||
cursor.hCursor != nullptr) {
|
mem_dc, cursor, request.left, request.top,
|
||||||
const int x = cursor.ptScreenPos.x - request.left;
|
request.width, request.height)) {
|
||||||
const int y = cursor.ptScreenPos.y - request.top;
|
cursor_snapshot.visible = 0;
|
||||||
if (x >= -64 && y >= -64 && x < request.width + 64 &&
|
cursor_snapshot.render_mode =
|
||||||
y < request.height + 64) {
|
static_cast<uint32_t>(crossdesk::CursorRenderMode::embedded);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Complete GDI writes before libyuv reads the DIB's pixel memory.
|
// Complete GDI writes before libyuv reads the DIB's pixel memory.
|
||||||
|
|||||||
Reference in New Issue
Block a user