mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-24 21:57:08 +08:00
[fix] restore Windows cursor capture and web input control
This commit is contained in:
@@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-09-18
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _CURSOR_FRAME_COMPOSITOR_H_
|
||||||
|
#define _CURSOR_FRAME_COMPOSITOR_H_
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include <cstdint>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "cursor_draw.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
// Keep the source texture free of cursors. Each draw starts from the original
|
||||||
|
// pixels, including when a static desktop is replayed after a cursor toggle.
|
||||||
|
class CursorFrameCompositor {
|
||||||
|
public:
|
||||||
|
CursorFrameCompositor() = default;
|
||||||
|
~CursorFrameCompositor() { Reset(); }
|
||||||
|
CursorFrameCompositor(const CursorFrameCompositor&) = delete;
|
||||||
|
CursorFrameCompositor& operator=(const CursorFrameCompositor&) = delete;
|
||||||
|
|
||||||
|
const uint8_t* Draw(const uint8_t* source, int stride, int width, int height,
|
||||||
|
const CURSORINFO& cursor, int left, int top) {
|
||||||
|
if (!source || width <= 0 || height <= 0 || !EnsureSurface(width, height))
|
||||||
|
return nullptr;
|
||||||
|
const size_t row_bytes = static_cast<size_t>(width) * 4;
|
||||||
|
for (int y = 0; y < height; ++y) {
|
||||||
|
std::memcpy(static_cast<uint8_t*>(bits_) + y * row_bytes,
|
||||||
|
source + static_cast<size_t>(y) * stride, row_bytes);
|
||||||
|
}
|
||||||
|
const bool drawn =
|
||||||
|
DrawCursorInCapture(dc_, cursor, left, top, width, height);
|
||||||
|
GdiFlush();
|
||||||
|
return drawn ? static_cast<const uint8_t*>(bits_) : nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool EnsureSurface(int width, int height) {
|
||||||
|
if (bits_ && width_ == width && height_ == height) return true;
|
||||||
|
Reset();
|
||||||
|
dc_ = CreateCompatibleDC(nullptr);
|
||||||
|
if (!dc_) return false;
|
||||||
|
BITMAPINFO info{};
|
||||||
|
info.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
info.bmiHeader.biWidth = width;
|
||||||
|
info.bmiHeader.biHeight = -height;
|
||||||
|
info.bmiHeader.biPlanes = 1;
|
||||||
|
info.bmiHeader.biBitCount = 32;
|
||||||
|
info.bmiHeader.biCompression = BI_RGB;
|
||||||
|
bitmap_ = CreateDIBSection(dc_, &info, DIB_RGB_COLORS, &bits_, nullptr, 0);
|
||||||
|
if (!bitmap_ || !bits_) {
|
||||||
|
Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
previous_ = SelectObject(dc_, bitmap_);
|
||||||
|
if (!previous_ || previous_ == HGDI_ERROR) {
|
||||||
|
previous_ = nullptr;
|
||||||
|
Reset();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
width_ = width;
|
||||||
|
height_ = height;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Reset() {
|
||||||
|
if (dc_ && previous_) SelectObject(dc_, previous_);
|
||||||
|
if (bitmap_) DeleteObject(bitmap_);
|
||||||
|
if (dc_) DeleteDC(dc_);
|
||||||
|
dc_ = nullptr;
|
||||||
|
bitmap_ = nullptr;
|
||||||
|
previous_ = nullptr;
|
||||||
|
bits_ = nullptr;
|
||||||
|
width_ = height_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
HDC dc_ = nullptr;
|
||||||
|
HBITMAP bitmap_ = nullptr;
|
||||||
|
HGDIOBJ previous_ = nullptr;
|
||||||
|
void* bits_ = nullptr;
|
||||||
|
int width_ = 0;
|
||||||
|
int height_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -7,9 +7,12 @@
|
|||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "captured_cursor_state.h"
|
||||||
|
#include "cursor_frame_compositor.h"
|
||||||
#include "dxgi_cursor_state.h"
|
#include "dxgi_cursor_state.h"
|
||||||
#include "libyuv.h"
|
#include "libyuv.h"
|
||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
|
#include "windows_thread_dpi.h"
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
@@ -295,12 +298,103 @@ void ScreenCapturerDxgi::ReleaseDuplication() {
|
|||||||
duplication_.Reset();
|
duplication_.Reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ScreenCapturerDxgi::ConvertFrame(int frame_monitor, bool* cursor_embedded,
|
||||||
|
CursorFrameCompositor& compositor) {
|
||||||
|
*cursor_embedded = false;
|
||||||
|
if (!staging_) return false;
|
||||||
|
D3D11_TEXTURE2D_DESC src_desc{};
|
||||||
|
staging_->GetDesc(&src_desc);
|
||||||
|
D3D11_MAPPED_SUBRESOURCE mapped{};
|
||||||
|
const HRESULT hr =
|
||||||
|
d3d_context_->Map(staging_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto pixels = static_cast<const uint8_t*>(mapped.pData);
|
||||||
|
int stride = static_cast<int>(mapped.RowPitch);
|
||||||
|
int logical_width = static_cast<int>(src_desc.Width);
|
||||||
|
int logical_height = static_cast<int>(src_desc.Height);
|
||||||
|
libyuv::RotationMode rotation = libyuv::kRotate0;
|
||||||
|
switch (rotation_) {
|
||||||
|
case DXGI_MODE_ROTATION_ROTATE90:
|
||||||
|
rotation = libyuv::kRotate90;
|
||||||
|
break;
|
||||||
|
case DXGI_MODE_ROTATION_ROTATE180:
|
||||||
|
rotation = libyuv::kRotate180;
|
||||||
|
break;
|
||||||
|
case DXGI_MODE_ROTATION_ROTATE270:
|
||||||
|
rotation = libyuv::kRotate270;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (rotation != libyuv::kRotate0) {
|
||||||
|
const bool swap_axes = rotation != libyuv::kRotate180;
|
||||||
|
const int rotated_width = swap_axes ? logical_height : logical_width;
|
||||||
|
rotated_frame_.resize(static_cast<size_t>(logical_width) * logical_height *
|
||||||
|
4);
|
||||||
|
if (libyuv::ARGBRotate(pixels, stride, rotated_frame_.data(),
|
||||||
|
rotated_width * 4, logical_width, logical_height,
|
||||||
|
rotation) != 0) {
|
||||||
|
d3d_context_->Unmap(staging_.Get(), 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
pixels = rotated_frame_.data();
|
||||||
|
stride = rotated_width * 4;
|
||||||
|
if (swap_axes) std::swap(logical_width, logical_height);
|
||||||
|
}
|
||||||
|
int even_width = logical_width & ~1;
|
||||||
|
int even_height = logical_height & ~1;
|
||||||
|
if (even_width <= 0 || even_height <= 0) {
|
||||||
|
d3d_context_->Unmap(staging_.Get(), 0);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (show_cursor_.load(std::memory_order_relaxed) && frame_monitor >= 0 &&
|
||||||
|
frame_monitor < static_cast<int>(display_info_list_.size())) {
|
||||||
|
const auto& display = display_info_list_[frame_monitor];
|
||||||
|
CURSORINFO cursor{};
|
||||||
|
cursor.cbSize = sizeof(cursor);
|
||||||
|
if (GetCursorInfo(&cursor) &&
|
||||||
|
SharedDxgiCursorState().ShouldDrawCursor(
|
||||||
|
(cursor.flags & CURSOR_SHOWING) != 0, display.handle)) {
|
||||||
|
if (const auto* composited =
|
||||||
|
compositor.Draw(pixels, stride, even_width, even_height, cursor,
|
||||||
|
display.left, display.top)) {
|
||||||
|
pixels = composited;
|
||||||
|
stride = even_width * 4;
|
||||||
|
*cursor_embedded = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int nv12_size = even_width * even_height * 3 / 2;
|
||||||
|
if (!nv12_frame_ || nv12_width_ != even_width ||
|
||||||
|
nv12_height_ != even_height) {
|
||||||
|
delete[] nv12_frame_;
|
||||||
|
nv12_frame_ = new unsigned char[nv12_size];
|
||||||
|
nv12_width_ = even_width;
|
||||||
|
nv12_height_ = even_height;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int converted =
|
||||||
|
libyuv::ARGBToNV12(pixels, stride, nv12_frame_, even_width,
|
||||||
|
nv12_frame_ + even_width * even_height, even_width,
|
||||||
|
even_width, even_height);
|
||||||
|
|
||||||
|
d3d_context_->Unmap(staging_.Get(), 0);
|
||||||
|
return converted == 0;
|
||||||
|
}
|
||||||
|
|
||||||
void ScreenCapturerDxgi::CaptureLoop() {
|
void ScreenCapturerDxgi::CaptureLoop() {
|
||||||
|
ScopedWindowsPhysicalCoordinates physical_coordinates;
|
||||||
|
CursorFrameCompositor cursor_compositor;
|
||||||
const int timeout_ms = (std::max)(1, 1000 / (std::max)(1, fps_));
|
const int timeout_ms = (std::max)(1, 1000 / (std::max)(1, fps_));
|
||||||
bool cached_frame_valid = false;
|
bool cached_frame_valid = false;
|
||||||
|
bool cached_cursor_embedded = false;
|
||||||
int cached_monitor = -1;
|
int cached_monitor = -1;
|
||||||
uint64_t cached_generation = 0;
|
uint64_t cached_generation = 0;
|
||||||
std::vector<uint8_t> rotated_frame;
|
|
||||||
auto last_duplication_retry =
|
auto last_duplication_retry =
|
||||||
std::chrono::steady_clock::now() - std::chrono::milliseconds(1000);
|
std::chrono::steady_clock::now() - std::chrono::milliseconds(1000);
|
||||||
while (running_) {
|
while (running_) {
|
||||||
@@ -336,8 +430,19 @@ void ScreenCapturerDxgi::CaptureLoop() {
|
|||||||
// successfully acquired for this monitor to keep delivering static frames.
|
// successfully acquired for this monitor to keep delivering static frames.
|
||||||
if (cached_frame_valid && cached_generation == duplication_generation_ &&
|
if (cached_frame_valid && cached_generation == duplication_generation_ &&
|
||||||
cached_monitor == monitor_index_.load() && callback_ && nv12_frame_) {
|
cached_monitor == monitor_index_.load() && callback_ && nv12_frame_) {
|
||||||
|
// Recompose from the clean staging texture while showing a cursor, or
|
||||||
|
// once after disabling it. Never replay a baked-in stale cursor.
|
||||||
|
if (show_cursor_.load(std::memory_order_relaxed) ||
|
||||||
|
cached_cursor_embedded) {
|
||||||
|
if (!ConvertFrame(cached_monitor, &cached_cursor_embedded,
|
||||||
|
cursor_compositor)) {
|
||||||
|
cached_frame_valid = false;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
const auto stream_id = MakeDisplayStreamId(cached_monitor);
|
const auto stream_id = MakeDisplayStreamId(cached_monitor);
|
||||||
capture_lock.unlock();
|
capture_lock.unlock();
|
||||||
|
CapturedCursorFrameScope cursor_scope(cached_cursor_embedded);
|
||||||
callback_(nv12_frame_, nv12_width_ * nv12_height_ * 3 / 2, nv12_width_,
|
callback_(nv12_frame_, nv12_width_ * nv12_height_ * 3 / 2, nv12_width_,
|
||||||
nv12_height_, stream_id.c_str(), nullptr);
|
nv12_height_, stream_id.c_str(), nullptr);
|
||||||
}
|
}
|
||||||
@@ -397,80 +502,22 @@ void ScreenCapturerDxgi::CaptureLoop() {
|
|||||||
|
|
||||||
d3d_context_->CopyResource(staging_.Get(), acquired_tex.Get());
|
d3d_context_->CopyResource(staging_.Get(), acquired_tex.Get());
|
||||||
|
|
||||||
D3D11_MAPPED_SUBRESOURCE mapped{};
|
bool cursor_embedded = false;
|
||||||
hr = d3d_context_->Map(staging_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
|
const bool converted =
|
||||||
if (FAILED(hr)) {
|
ConvertFrame(frame_monitor, &cursor_embedded, cursor_compositor);
|
||||||
duplication_->ReleaseFrame();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto pixels = static_cast<const uint8_t*>(mapped.pData);
|
|
||||||
int stride = static_cast<int>(mapped.RowPitch);
|
|
||||||
int logical_width = static_cast<int>(src_desc.Width);
|
|
||||||
int logical_height = static_cast<int>(src_desc.Height);
|
|
||||||
libyuv::RotationMode rotation = libyuv::kRotate0;
|
|
||||||
switch (rotation_) {
|
|
||||||
case DXGI_MODE_ROTATION_ROTATE90:
|
|
||||||
rotation = libyuv::kRotate90;
|
|
||||||
break;
|
|
||||||
case DXGI_MODE_ROTATION_ROTATE180:
|
|
||||||
rotation = libyuv::kRotate180;
|
|
||||||
break;
|
|
||||||
case DXGI_MODE_ROTATION_ROTATE270:
|
|
||||||
rotation = libyuv::kRotate270;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
if (rotation != libyuv::kRotate0) {
|
|
||||||
const bool swap_axes = rotation != libyuv::kRotate180;
|
|
||||||
const int rotated_width = swap_axes ? logical_height : logical_width;
|
|
||||||
rotated_frame.resize(static_cast<size_t>(logical_width) * logical_height *
|
|
||||||
4);
|
|
||||||
if (libyuv::ARGBRotate(pixels, stride, rotated_frame.data(),
|
|
||||||
rotated_width * 4, logical_width, logical_height,
|
|
||||||
rotation) != 0) {
|
|
||||||
d3d_context_->Unmap(staging_.Get(), 0);
|
|
||||||
duplication_->ReleaseFrame();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
pixels = rotated_frame.data();
|
|
||||||
stride = rotated_width * 4;
|
|
||||||
if (swap_axes) std::swap(logical_width, logical_height);
|
|
||||||
}
|
|
||||||
int even_width = logical_width & ~1;
|
|
||||||
int even_height = logical_height & ~1;
|
|
||||||
if (even_width <= 0 || even_height <= 0) {
|
|
||||||
d3d_context_->Unmap(staging_.Get(), 0);
|
|
||||||
duplication_->ReleaseFrame();
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int nv12_size = even_width * even_height * 3 / 2;
|
|
||||||
if (!nv12_frame_ || nv12_width_ != even_width ||
|
|
||||||
nv12_height_ != even_height) {
|
|
||||||
delete[] nv12_frame_;
|
|
||||||
nv12_frame_ = new unsigned char[nv12_size];
|
|
||||||
nv12_width_ = even_width;
|
|
||||||
nv12_height_ = even_height;
|
|
||||||
}
|
|
||||||
|
|
||||||
const int converted =
|
|
||||||
libyuv::ARGBToNV12(pixels, stride, nv12_frame_, even_width,
|
|
||||||
nv12_frame_ + even_width * even_height, even_width,
|
|
||||||
even_width, even_height);
|
|
||||||
|
|
||||||
cached_generation = duplication_generation_;
|
cached_generation = duplication_generation_;
|
||||||
d3d_context_->Unmap(staging_.Get(), 0);
|
|
||||||
duplication_->ReleaseFrame();
|
duplication_->ReleaseFrame();
|
||||||
capture_lock.unlock();
|
capture_lock.unlock();
|
||||||
|
|
||||||
if (converted == 0 && frame_monitor == monitor_index_.load() && callback_) {
|
if (converted && frame_monitor == monitor_index_.load() && callback_) {
|
||||||
int idx = frame_monitor;
|
int idx = frame_monitor;
|
||||||
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
|
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
|
||||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||||
callback_(nv12_frame_, nv12_size, even_width, even_height,
|
CapturedCursorFrameScope cursor_scope(cursor_embedded);
|
||||||
stream_id.c_str(), nullptr);
|
callback_(nv12_frame_, nv12_width_ * nv12_height_ * 3 / 2, nv12_width_,
|
||||||
|
nv12_height_, stream_id.c_str(), nullptr);
|
||||||
|
cached_cursor_embedded = cursor_embedded;
|
||||||
cached_monitor = idx;
|
cached_monitor = idx;
|
||||||
cached_frame_valid = true;
|
cached_frame_valid = true;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -25,6 +25,8 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
|
class CursorFrameCompositor;
|
||||||
|
|
||||||
class ScreenCapturerDxgi : public ScreenCapturer {
|
class ScreenCapturerDxgi : public ScreenCapturer {
|
||||||
public:
|
public:
|
||||||
ScreenCapturerDxgi();
|
ScreenCapturerDxgi();
|
||||||
@@ -35,6 +37,9 @@ class ScreenCapturerDxgi : 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;
|
||||||
@@ -52,6 +57,8 @@ class ScreenCapturerDxgi : public ScreenCapturer {
|
|||||||
bool CreateDuplicationForMonitor(int monitor_index);
|
bool CreateDuplicationForMonitor(int monitor_index);
|
||||||
bool RecreateDuplicationForCurrentMonitor();
|
bool RecreateDuplicationForCurrentMonitor();
|
||||||
void CaptureLoop();
|
void CaptureLoop();
|
||||||
|
bool ConvertFrame(int frame_monitor, bool* cursor_embedded,
|
||||||
|
CursorFrameCompositor& compositor);
|
||||||
void ReleaseDuplication();
|
void ReleaseDuplication();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
@@ -79,6 +86,7 @@ class ScreenCapturerDxgi : public ScreenCapturer {
|
|||||||
unsigned char* nv12_frame_ = nullptr;
|
unsigned char* nv12_frame_ = nullptr;
|
||||||
int nv12_width_ = 0;
|
int nv12_width_ = 0;
|
||||||
int nv12_height_ = 0;
|
int nv12_height_ = 0;
|
||||||
|
std::vector<uint8_t> rotated_frame_;
|
||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
|||||||
@@ -902,6 +902,8 @@ void ScreenCapturerWin::ApplyCursorCaptureSetting() {
|
|||||||
int ret = 0;
|
int ret = 0;
|
||||||
if (auto* gdi = dynamic_cast<ScreenCapturerGdi*>(impl_.get())) {
|
if (auto* gdi = dynamic_cast<ScreenCapturerGdi*>(impl_.get())) {
|
||||||
gdi->SetCursorCapture(requested);
|
gdi->SetCursorCapture(requested);
|
||||||
|
} else if (auto* dxgi = dynamic_cast<ScreenCapturerDxgi*>(impl_.get())) {
|
||||||
|
dxgi->SetCursorCapture(requested);
|
||||||
} else if (auto* wgc = dynamic_cast<WgcPluginCapturer*>(impl_.get())) {
|
} else if (auto* wgc = dynamic_cast<WgcPluginCapturer*>(impl_.get())) {
|
||||||
ret = wgc->SetCursorCapture(requested);
|
ret = wgc->SetCursorCapture(requested);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -230,7 +230,8 @@ function setup_targets()
|
|||||||
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp",
|
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_gdi.cpp",
|
||||||
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp",
|
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_win.cpp",
|
||||||
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_factory.cpp")
|
"apps/desktop/src/platform/windows/screen_capturer/screen_capturer_factory.cpp")
|
||||||
add_includedirs("apps/desktop/src/platform/windows/screen_capturer",
|
add_includedirs("apps/desktop/src/platform/windows",
|
||||||
|
"apps/desktop/src/platform/windows/screen_capturer",
|
||||||
"apps/desktop/src/platform/windows/service")
|
"apps/desktop/src/platform/windows/service")
|
||||||
elseif is_os("macosx") then
|
elseif is_os("macosx") then
|
||||||
add_deps("privacy")
|
add_deps("privacy")
|
||||||
|
|||||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 96b765f818...a25a3b4c4a
Reference in New Issue
Block a user