mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-10-15 00:09:39 +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 <vector>
|
||||
|
||||
#include "captured_cursor_state.h"
|
||||
#include "cursor_frame_compositor.h"
|
||||
#include "dxgi_cursor_state.h"
|
||||
#include "libyuv.h"
|
||||
#include "rd_log.h"
|
||||
#include "windows_thread_dpi.h"
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
@@ -295,12 +298,103 @@ void ScreenCapturerDxgi::ReleaseDuplication() {
|
||||
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() {
|
||||
ScopedWindowsPhysicalCoordinates physical_coordinates;
|
||||
CursorFrameCompositor cursor_compositor;
|
||||
const int timeout_ms = (std::max)(1, 1000 / (std::max)(1, fps_));
|
||||
bool cached_frame_valid = false;
|
||||
bool cached_cursor_embedded = false;
|
||||
int cached_monitor = -1;
|
||||
uint64_t cached_generation = 0;
|
||||
std::vector<uint8_t> rotated_frame;
|
||||
auto last_duplication_retry =
|
||||
std::chrono::steady_clock::now() - std::chrono::milliseconds(1000);
|
||||
while (running_) {
|
||||
@@ -336,8 +430,19 @@ void ScreenCapturerDxgi::CaptureLoop() {
|
||||
// successfully acquired for this monitor to keep delivering static frames.
|
||||
if (cached_frame_valid && cached_generation == duplication_generation_ &&
|
||||
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);
|
||||
capture_lock.unlock();
|
||||
CapturedCursorFrameScope cursor_scope(cached_cursor_embedded);
|
||||
callback_(nv12_frame_, nv12_width_ * nv12_height_ * 3 / 2, nv12_width_,
|
||||
nv12_height_, stream_id.c_str(), nullptr);
|
||||
}
|
||||
@@ -397,80 +502,22 @@ void ScreenCapturerDxgi::CaptureLoop() {
|
||||
|
||||
d3d_context_->CopyResource(staging_.Get(), acquired_tex.Get());
|
||||
|
||||
D3D11_MAPPED_SUBRESOURCE mapped{};
|
||||
hr = d3d_context_->Map(staging_.Get(), 0, D3D11_MAP_READ, 0, &mapped);
|
||||
if (FAILED(hr)) {
|
||||
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);
|
||||
bool cursor_embedded = false;
|
||||
const bool converted =
|
||||
ConvertFrame(frame_monitor, &cursor_embedded, cursor_compositor);
|
||||
|
||||
cached_generation = duplication_generation_;
|
||||
d3d_context_->Unmap(staging_.Get(), 0);
|
||||
duplication_->ReleaseFrame();
|
||||
capture_lock.unlock();
|
||||
|
||||
if (converted == 0 && frame_monitor == monitor_index_.load() && callback_) {
|
||||
if (converted && frame_monitor == monitor_index_.load() && callback_) {
|
||||
int idx = frame_monitor;
|
||||
if (idx >= 0 && idx < static_cast<int>(display_info_list_.size())) {
|
||||
const std::string stream_id = MakeDisplayStreamId(idx);
|
||||
callback_(nv12_frame_, nv12_size, even_width, even_height,
|
||||
stream_id.c_str(), nullptr);
|
||||
CapturedCursorFrameScope cursor_scope(cursor_embedded);
|
||||
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_frame_valid = true;
|
||||
} else {
|
||||
|
||||
@@ -25,6 +25,8 @@
|
||||
|
||||
namespace crossdesk {
|
||||
|
||||
class CursorFrameCompositor;
|
||||
|
||||
class ScreenCapturerDxgi : public ScreenCapturer {
|
||||
public:
|
||||
ScreenCapturerDxgi();
|
||||
@@ -35,6 +37,9 @@ class ScreenCapturerDxgi : public ScreenCapturer {
|
||||
int Destroy() override;
|
||||
int Start(bool show_cursor) override;
|
||||
int Stop() override;
|
||||
void SetCursorCapture(bool enabled) {
|
||||
show_cursor_.store(enabled, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
int Pause(int monitor_index) override;
|
||||
int Resume(int monitor_index) override;
|
||||
@@ -52,6 +57,8 @@ class ScreenCapturerDxgi : public ScreenCapturer {
|
||||
bool CreateDuplicationForMonitor(int monitor_index);
|
||||
bool RecreateDuplicationForCurrentMonitor();
|
||||
void CaptureLoop();
|
||||
bool ConvertFrame(int frame_monitor, bool* cursor_embedded,
|
||||
CursorFrameCompositor& compositor);
|
||||
void ReleaseDuplication();
|
||||
|
||||
private:
|
||||
@@ -79,6 +86,7 @@ class ScreenCapturerDxgi : public ScreenCapturer {
|
||||
unsigned char* nv12_frame_ = nullptr;
|
||||
int nv12_width_ = 0;
|
||||
int nv12_height_ = 0;
|
||||
std::vector<uint8_t> rotated_frame_;
|
||||
};
|
||||
} // namespace crossdesk
|
||||
|
||||
|
||||
@@ -902,6 +902,8 @@ void ScreenCapturerWin::ApplyCursorCaptureSetting() {
|
||||
int ret = 0;
|
||||
if (auto* gdi = dynamic_cast<ScreenCapturerGdi*>(impl_.get())) {
|
||||
gdi->SetCursorCapture(requested);
|
||||
} else if (auto* dxgi = dynamic_cast<ScreenCapturerDxgi*>(impl_.get())) {
|
||||
dxgi->SetCursorCapture(requested);
|
||||
} else if (auto* wgc = dynamic_cast<WgcPluginCapturer*>(impl_.get())) {
|
||||
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_win.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")
|
||||
elseif is_os("macosx") then
|
||||
add_deps("privacy")
|
||||
|
||||
Vendored
+1
-1
Submodule deps/submodules/minirtc updated: 96b765f818...a25a3b4c4a
Reference in New Issue
Block a user