mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-04-10 12:30:37 +08:00
[feat] add Windows DXGI/GDI screen capture with WGC→DXGI→GDI fallback support
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
#define _SCREEN_CAPTURER_FACTORY_H_
|
#define _SCREEN_CAPTURER_FACTORY_H_
|
||||||
|
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
#include "screen_capturer_wgc.h"
|
#include "screen_capturer_win.h"
|
||||||
#elif __linux__
|
#elif __linux__
|
||||||
#include "screen_capturer_x11.h"
|
#include "screen_capturer_x11.h"
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
@@ -25,7 +25,7 @@ class ScreenCapturerFactory {
|
|||||||
public:
|
public:
|
||||||
ScreenCapturer* Create() {
|
ScreenCapturer* Create() {
|
||||||
#ifdef _WIN32
|
#ifdef _WIN32
|
||||||
return new ScreenCapturerWgc();
|
return new ScreenCapturerWin();
|
||||||
#elif __linux__
|
#elif __linux__
|
||||||
return new ScreenCapturerX11();
|
return new ScreenCapturerX11();
|
||||||
#elif __APPLE__
|
#elif __APPLE__
|
||||||
|
|||||||
333
src/screen_capturer/windows/screen_capturer_dxgi.cpp
Normal file
333
src/screen_capturer/windows/screen_capturer_dxgi.cpp
Normal file
@@ -0,0 +1,333 @@
|
|||||||
|
#include "screen_capturer_dxgi.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "libyuv.h"
|
||||||
|
#include "rd_log.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string WideToUtf8(const std::wstring& wstr) {
|
||||||
|
if (wstr.empty()) return {};
|
||||||
|
int size_needed = WideCharToMultiByte(
|
||||||
|
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||||
|
std::string result(size_needed, 0);
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
|
||||||
|
size_needed, nullptr, nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CleanDisplayName(const std::wstring& wide_name) {
|
||||||
|
std::string name = WideToUtf8(wide_name);
|
||||||
|
name.erase(std::remove_if(name.begin(), name.end(),
|
||||||
|
[](unsigned char c) { return !std::isalnum(c); }),
|
||||||
|
name.end());
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ScreenCapturerDxgi::ScreenCapturerDxgi() {}
|
||||||
|
ScreenCapturerDxgi::~ScreenCapturerDxgi() {
|
||||||
|
Stop();
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::Init(const int fps, cb_desktop_data cb) {
|
||||||
|
fps_ = fps;
|
||||||
|
callback_ = cb;
|
||||||
|
if (!callback_) {
|
||||||
|
LOG_ERROR("DXGI: callback is null");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!InitializeDxgi()) {
|
||||||
|
LOG_ERROR("DXGI: initialize DXGI failed");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
|
||||||
|
EnumerateDisplays();
|
||||||
|
if (display_info_list_.empty()) {
|
||||||
|
LOG_ERROR("DXGI: no displays found");
|
||||||
|
return -3;
|
||||||
|
}
|
||||||
|
|
||||||
|
monitor_index_ = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::Destroy() {
|
||||||
|
Stop();
|
||||||
|
ReleaseDuplication();
|
||||||
|
outputs_.clear();
|
||||||
|
d3d_context_.Reset();
|
||||||
|
d3d_device_.Reset();
|
||||||
|
dxgi_factory_.Reset();
|
||||||
|
if (nv12_frame_) {
|
||||||
|
delete[] nv12_frame_;
|
||||||
|
nv12_frame_ = nullptr;
|
||||||
|
nv12_width_ = 0;
|
||||||
|
nv12_height_ = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::Start(bool show_cursor) {
|
||||||
|
if (running_) return 0;
|
||||||
|
show_cursor_ = show_cursor;
|
||||||
|
|
||||||
|
if (!CreateDuplicationForMonitor(monitor_index_)) {
|
||||||
|
LOG_ERROR("DXGI: create duplication failed for monitor {}",
|
||||||
|
monitor_index_.load());
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
paused_ = false;
|
||||||
|
running_ = true;
|
||||||
|
thread_ = std::thread([this]() { CaptureLoop(); });
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::Stop() {
|
||||||
|
if (!running_) return 0;
|
||||||
|
running_ = false;
|
||||||
|
if (thread_.joinable()) thread_.join();
|
||||||
|
ReleaseDuplication();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::Pause(int monitor_index) {
|
||||||
|
paused_ = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::Resume(int monitor_index) {
|
||||||
|
paused_ = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::SwitchTo(int monitor_index) {
|
||||||
|
if (monitor_index < 0 || monitor_index >= (int)display_info_list_.size()) {
|
||||||
|
LOG_ERROR("DXGI: invalid monitor index {}", monitor_index);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
paused_ = true;
|
||||||
|
monitor_index_ = monitor_index;
|
||||||
|
ReleaseDuplication();
|
||||||
|
if (!CreateDuplicationForMonitor(monitor_index_)) {
|
||||||
|
LOG_ERROR("DXGI: create duplication failed for monitor {}",
|
||||||
|
monitor_index_.load());
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
paused_ = false;
|
||||||
|
LOG_INFO("DXGI: switched to monitor {}:{}", monitor_index_.load(),
|
||||||
|
display_info_list_[monitor_index_].name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScreenCapturerDxgi::InitializeDxgi() {
|
||||||
|
UINT flags = D3D11_CREATE_DEVICE_BGRA_SUPPORT;
|
||||||
|
#ifdef _DEBUG
|
||||||
|
flags |= D3D11_CREATE_DEVICE_DEBUG;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
D3D_FEATURE_LEVEL feature_levels[] = {
|
||||||
|
D3D_FEATURE_LEVEL_11_1, D3D_FEATURE_LEVEL_11_0, D3D_FEATURE_LEVEL_10_1,
|
||||||
|
D3D_FEATURE_LEVEL_10_0};
|
||||||
|
|
||||||
|
D3D_FEATURE_LEVEL out_level{};
|
||||||
|
HRESULT hr = D3D11CreateDevice(
|
||||||
|
nullptr, D3D_DRIVER_TYPE_HARDWARE, nullptr, flags, feature_levels,
|
||||||
|
ARRAYSIZE(feature_levels), D3D11_SDK_VERSION, d3d_device_.GetAddressOf(),
|
||||||
|
&out_level, d3d_context_.GetAddressOf());
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
hr = D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_WARP, nullptr, flags,
|
||||||
|
feature_levels, ARRAYSIZE(feature_levels),
|
||||||
|
D3D11_SDK_VERSION, d3d_device_.GetAddressOf(),
|
||||||
|
&out_level, d3d_context_.GetAddressOf());
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
LOG_ERROR("DXGI: D3D11CreateDevice failed, hr={}", (int)hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr = CreateDXGIFactory1(
|
||||||
|
__uuidof(IDXGIFactory1),
|
||||||
|
reinterpret_cast<void**>(dxgi_factory_.GetAddressOf()));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
LOG_ERROR("DXGI: CreateDXGIFactory1 failed, hr={}", (int)hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerDxgi::EnumerateDisplays() {
|
||||||
|
display_info_list_.clear();
|
||||||
|
outputs_.clear();
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIAdapter> adapter;
|
||||||
|
for (UINT a = 0;
|
||||||
|
dxgi_factory_->EnumAdapters(a, adapter.ReleaseAndGetAddressOf()) !=
|
||||||
|
DXGI_ERROR_NOT_FOUND;
|
||||||
|
++a) {
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIOutput> output;
|
||||||
|
for (UINT o = 0; adapter->EnumOutputs(o, output.ReleaseAndGetAddressOf()) !=
|
||||||
|
DXGI_ERROR_NOT_FOUND;
|
||||||
|
++o) {
|
||||||
|
DXGI_OUTPUT_DESC desc{};
|
||||||
|
if (FAILED(output->GetDesc(&desc))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string name = CleanDisplayName(desc.DeviceName);
|
||||||
|
MONITORINFOEX mi{};
|
||||||
|
mi.cbSize = sizeof(MONITORINFOEX);
|
||||||
|
if (GetMonitorInfo(desc.Monitor, &mi)) {
|
||||||
|
bool is_primary = (mi.dwFlags & MONITORINFOF_PRIMARY) ? true : false;
|
||||||
|
DisplayInfo info((void*)desc.Monitor, name, is_primary,
|
||||||
|
mi.rcMonitor.left, mi.rcMonitor.top,
|
||||||
|
mi.rcMonitor.right, mi.rcMonitor.bottom);
|
||||||
|
// primary first
|
||||||
|
if (is_primary)
|
||||||
|
display_info_list_.insert(display_info_list_.begin(), info);
|
||||||
|
else
|
||||||
|
display_info_list_.push_back(info);
|
||||||
|
outputs_.push_back(output);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool ScreenCapturerDxgi::CreateDuplicationForMonitor(int monitor_index) {
|
||||||
|
if (monitor_index < 0 || monitor_index >= (int)outputs_.size()) return false;
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIOutput1> output1;
|
||||||
|
HRESULT hr = outputs_[monitor_index]->QueryInterface(
|
||||||
|
IID_PPV_ARGS(output1.GetAddressOf()));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
LOG_ERROR("DXGI: Query IDXGIOutput1 failed, hr={}", (int)hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
duplication_.Reset();
|
||||||
|
hr = output1->DuplicateOutput(d3d_device_.Get(), duplication_.GetAddressOf());
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
LOG_ERROR("DXGI: DuplicateOutput failed, hr={}", (int)hr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
staging_.Reset();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerDxgi::ReleaseDuplication() {
|
||||||
|
staging_.Reset();
|
||||||
|
if (duplication_) {
|
||||||
|
duplication_->ReleaseFrame();
|
||||||
|
}
|
||||||
|
duplication_.Reset();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerDxgi::CaptureLoop() {
|
||||||
|
const int timeout_ms = 33;
|
||||||
|
while (running_) {
|
||||||
|
if (paused_) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!duplication_) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
DXGI_OUTDUPL_FRAME_INFO frame_info{};
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIResource> desktop_resource;
|
||||||
|
HRESULT hr = duplication_->AcquireNextFrame(
|
||||||
|
timeout_ms, &frame_info, desktop_resource.GetAddressOf());
|
||||||
|
if (hr == DXGI_ERROR_WAIT_TIMEOUT) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
LOG_ERROR("DXGI: AcquireNextFrame failed, hr={}", (int)hr);
|
||||||
|
// attempt to recreate duplication
|
||||||
|
ReleaseDuplication();
|
||||||
|
CreateDuplicationForMonitor(monitor_index_);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Texture2D> acquired_tex;
|
||||||
|
if (desktop_resource) {
|
||||||
|
hr = desktop_resource->QueryInterface(
|
||||||
|
IID_PPV_ARGS(acquired_tex.GetAddressOf()));
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
duplication_->ReleaseFrame();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
duplication_->ReleaseFrame();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
D3D11_TEXTURE2D_DESC src_desc{};
|
||||||
|
acquired_tex->GetDesc(&src_desc);
|
||||||
|
|
||||||
|
if (!staging_) {
|
||||||
|
D3D11_TEXTURE2D_DESC staging_desc = src_desc;
|
||||||
|
staging_desc.Usage = D3D11_USAGE_STAGING;
|
||||||
|
staging_desc.BindFlags = 0;
|
||||||
|
staging_desc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
|
||||||
|
staging_desc.MiscFlags = 0;
|
||||||
|
hr = d3d_device_->CreateTexture2D(&staging_desc, nullptr,
|
||||||
|
staging_.GetAddressOf());
|
||||||
|
if (FAILED(hr)) {
|
||||||
|
LOG_ERROR("DXGI: CreateTexture2D staging failed, hr={}", (int)hr);
|
||||||
|
duplication_->ReleaseFrame();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int logical_width = static_cast<int>(src_desc.Width);
|
||||||
|
int even_width = logical_width & ~1;
|
||||||
|
int even_height = static_cast<int>(src_desc.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;
|
||||||
|
}
|
||||||
|
|
||||||
|
libyuv::ARGBToNV12(static_cast<const uint8_t*>(mapped.pData),
|
||||||
|
static_cast<int>(mapped.RowPitch), nv12_frame_,
|
||||||
|
even_width, nv12_frame_ + even_width * even_height,
|
||||||
|
even_width, even_width, even_height);
|
||||||
|
|
||||||
|
if (callback_) {
|
||||||
|
callback_(nv12_frame_, nv12_size, even_width, even_height,
|
||||||
|
display_info_list_[monitor_index_].name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
d3d_context_->Unmap(staging_.Get(), 0);
|
||||||
|
duplication_->ReleaseFrame();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
79
src/screen_capturer/windows/screen_capturer_dxgi.h
Normal file
79
src/screen_capturer/windows/screen_capturer_dxgi.h
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-02-27
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SCREEN_CAPTURER_DXGI_H_
|
||||||
|
#define _SCREEN_CAPTURER_DXGI_H_
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
#include <d3d11.h>
|
||||||
|
#include <dxgi1_2.h>
|
||||||
|
#include <wrl/client.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "rd_log.h"
|
||||||
|
#include "screen_capturer.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
class ScreenCapturerDxgi : public ScreenCapturer {
|
||||||
|
public:
|
||||||
|
ScreenCapturerDxgi();
|
||||||
|
~ScreenCapturerDxgi();
|
||||||
|
|
||||||
|
public:
|
||||||
|
int Init(const int fps, cb_desktop_data cb) override;
|
||||||
|
int Destroy() override;
|
||||||
|
int Start(bool show_cursor) override;
|
||||||
|
int Stop() override;
|
||||||
|
|
||||||
|
int Pause(int monitor_index) override;
|
||||||
|
int Resume(int monitor_index) override;
|
||||||
|
|
||||||
|
int SwitchTo(int monitor_index) override;
|
||||||
|
|
||||||
|
std::vector<DisplayInfo> GetDisplayInfoList() override {
|
||||||
|
return display_info_list_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
bool InitializeDxgi();
|
||||||
|
void EnumerateDisplays();
|
||||||
|
bool CreateDuplicationForMonitor(int monitor_index);
|
||||||
|
void CaptureLoop();
|
||||||
|
void ReleaseDuplication();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
|
std::vector<Microsoft::WRL::ComPtr<IDXGIOutput>> outputs_;
|
||||||
|
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIFactory1> dxgi_factory_;
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Device> d3d_device_;
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11DeviceContext> d3d_context_;
|
||||||
|
Microsoft::WRL::ComPtr<IDXGIOutputDuplication> duplication_;
|
||||||
|
Microsoft::WRL::ComPtr<ID3D11Texture2D> staging_;
|
||||||
|
|
||||||
|
std::atomic<bool> running_{false};
|
||||||
|
std::atomic<bool> paused_{false};
|
||||||
|
std::atomic<int> monitor_index_{0};
|
||||||
|
std::atomic<bool> show_cursor_{true};
|
||||||
|
std::thread thread_;
|
||||||
|
int fps_ = 60;
|
||||||
|
cb_desktop_data callback_ = nullptr;
|
||||||
|
|
||||||
|
unsigned char* nv12_frame_ = nullptr;
|
||||||
|
int nv12_width_ = 0;
|
||||||
|
int nv12_height_ = 0;
|
||||||
|
};
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
206
src/screen_capturer/windows/screen_capturer_gdi.cpp
Normal file
206
src/screen_capturer/windows/screen_capturer_gdi.cpp
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
#include "screen_capturer_gdi.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "libyuv.h"
|
||||||
|
#include "rd_log.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
std::string WideToUtf8(const std::wstring& wstr) {
|
||||||
|
if (wstr.empty()) return {};
|
||||||
|
int size_needed = WideCharToMultiByte(
|
||||||
|
CP_UTF8, 0, wstr.data(), (int)wstr.size(), nullptr, 0, nullptr, nullptr);
|
||||||
|
std::string result(size_needed, 0);
|
||||||
|
WideCharToMultiByte(CP_UTF8, 0, wstr.data(), (int)wstr.size(), result.data(),
|
||||||
|
size_needed, nullptr, nullptr);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string CleanDisplayName(const std::wstring& wide_name) {
|
||||||
|
std::string name = WideToUtf8(wide_name);
|
||||||
|
name.erase(std::remove_if(name.begin(), name.end(),
|
||||||
|
[](unsigned char c) { return !std::isalnum(c); }),
|
||||||
|
name.end());
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
|
ScreenCapturerGdi::ScreenCapturerGdi() {}
|
||||||
|
ScreenCapturerGdi::~ScreenCapturerGdi() {
|
||||||
|
Stop();
|
||||||
|
Destroy();
|
||||||
|
}
|
||||||
|
|
||||||
|
BOOL CALLBACK ScreenCapturerGdi::EnumMonitorProc(HMONITOR hMonitor, HDC, LPRECT,
|
||||||
|
LPARAM data) {
|
||||||
|
auto displays = reinterpret_cast<std::vector<DisplayInfo>*>(data);
|
||||||
|
MONITORINFOEX mi{};
|
||||||
|
mi.cbSize = sizeof(MONITORINFOEX);
|
||||||
|
if (GetMonitorInfo(hMonitor, &mi)) {
|
||||||
|
std::string name = CleanDisplayName(mi.szDevice);
|
||||||
|
bool is_primary = (mi.dwFlags & MONITORINFOF_PRIMARY) ? true : false;
|
||||||
|
DisplayInfo info((void*)hMonitor, name, is_primary, mi.rcMonitor.left,
|
||||||
|
mi.rcMonitor.top, mi.rcMonitor.right, mi.rcMonitor.bottom);
|
||||||
|
if (is_primary)
|
||||||
|
displays->insert(displays->begin(), info);
|
||||||
|
else
|
||||||
|
displays->push_back(info);
|
||||||
|
}
|
||||||
|
return TRUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerGdi::EnumerateDisplays() {
|
||||||
|
display_info_list_.clear();
|
||||||
|
EnumDisplayMonitors(nullptr, nullptr, EnumMonitorProc,
|
||||||
|
(LPARAM)&display_info_list_);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::Init(const int fps, cb_desktop_data cb) {
|
||||||
|
fps_ = fps;
|
||||||
|
callback_ = cb;
|
||||||
|
if (!callback_) {
|
||||||
|
LOG_ERROR("GDI: callback is null");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
EnumerateDisplays();
|
||||||
|
if (display_info_list_.empty()) {
|
||||||
|
LOG_ERROR("GDI: no displays found");
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
monitor_index_ = 0;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::Destroy() {
|
||||||
|
Stop();
|
||||||
|
if (nv12_frame_) {
|
||||||
|
delete[] nv12_frame_;
|
||||||
|
nv12_frame_ = nullptr;
|
||||||
|
nv12_width_ = 0;
|
||||||
|
nv12_height_ = 0;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::Start(bool show_cursor) {
|
||||||
|
if (running_) return 0;
|
||||||
|
show_cursor_ = show_cursor;
|
||||||
|
paused_ = false;
|
||||||
|
running_ = true;
|
||||||
|
thread_ = std::thread([this]() { CaptureLoop(); });
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::Stop() {
|
||||||
|
if (!running_) return 0;
|
||||||
|
running_ = false;
|
||||||
|
if (thread_.joinable()) thread_.join();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::Pause(int monitor_index) {
|
||||||
|
paused_ = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::Resume(int monitor_index) {
|
||||||
|
paused_ = false;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::SwitchTo(int monitor_index) {
|
||||||
|
if (monitor_index < 0 || monitor_index >= (int)display_info_list_.size()) {
|
||||||
|
LOG_ERROR("GDI: invalid monitor index {}", monitor_index);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
monitor_index_ = monitor_index;
|
||||||
|
LOG_INFO("GDI: switched to monitor {}:{}", monitor_index_.load(),
|
||||||
|
display_info_list_[monitor_index_].name);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerGdi::CaptureLoop() {
|
||||||
|
int interval_ms = fps_ > 0 ? (1000 / fps_) : 16;
|
||||||
|
HDC screen_dc = GetDC(nullptr);
|
||||||
|
while (running_) {
|
||||||
|
if (paused_) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!screen_dc) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(10));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const auto& di = display_info_list_[monitor_index_];
|
||||||
|
int left = di.left;
|
||||||
|
int top = di.top;
|
||||||
|
int width = di.width & ~1;
|
||||||
|
int height = di.height & ~1;
|
||||||
|
if (width <= 0 || height <= 0) {
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
BITMAPINFO bmi{};
|
||||||
|
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
|
||||||
|
bmi.bmiHeader.biWidth = width;
|
||||||
|
bmi.bmiHeader.biHeight = -height;
|
||||||
|
bmi.bmiHeader.biPlanes = 1;
|
||||||
|
bmi.bmiHeader.biBitCount = 32;
|
||||||
|
bmi.bmiHeader.biCompression = BI_RGB;
|
||||||
|
|
||||||
|
void* bits = nullptr;
|
||||||
|
HDC mem_dc = CreateCompatibleDC(screen_dc);
|
||||||
|
HBITMAP dib =
|
||||||
|
CreateDIBSection(mem_dc, &bmi, DIB_RGB_COLORS, &bits, nullptr, 0);
|
||||||
|
HGDIOBJ old = SelectObject(mem_dc, dib);
|
||||||
|
|
||||||
|
BitBlt(mem_dc, 0, 0, width, height, screen_dc, left, top,
|
||||||
|
SRCCOPY | CAPTUREBLT);
|
||||||
|
|
||||||
|
if (show_cursor_) {
|
||||||
|
CURSORINFO ci{};
|
||||||
|
ci.cbSize = sizeof(CURSORINFO);
|
||||||
|
if (GetCursorInfo(&ci) && ci.flags == CURSOR_SHOWING && ci.hCursor) {
|
||||||
|
POINT pt = ci.ptScreenPos;
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int stride_argb = width * 4;
|
||||||
|
int nv12_size = width * height * 3 / 2;
|
||||||
|
if (!nv12_frame_ || nv12_width_ != width || nv12_height_ != height) {
|
||||||
|
delete[] nv12_frame_;
|
||||||
|
nv12_frame_ = new unsigned char[nv12_size];
|
||||||
|
nv12_width_ = width;
|
||||||
|
nv12_height_ = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
libyuv::ARGBToNV12(static_cast<const uint8_t*>(bits), stride_argb,
|
||||||
|
nv12_frame_, width, nv12_frame_ + width * height, width,
|
||||||
|
width, height);
|
||||||
|
|
||||||
|
if (callback_) {
|
||||||
|
callback_(nv12_frame_, nv12_size, width, height, di.name.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
SelectObject(mem_dc, old);
|
||||||
|
DeleteObject(dib);
|
||||||
|
DeleteDC(mem_dc);
|
||||||
|
|
||||||
|
std::this_thread::sleep_for(std::chrono::milliseconds(interval_ms));
|
||||||
|
}
|
||||||
|
ReleaseDC(nullptr, screen_dc);
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
66
src/screen_capturer/windows/screen_capturer_gdi.h
Normal file
66
src/screen_capturer/windows/screen_capturer_gdi.h
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-02-27
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SCREEN_CAPTURER_GDI_H_
|
||||||
|
#define _SCREEN_CAPTURER_GDI_H_
|
||||||
|
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
#include <atomic>
|
||||||
|
#include <functional>
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "rd_log.h"
|
||||||
|
#include "screen_capturer.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
class ScreenCapturerGdi : public ScreenCapturer {
|
||||||
|
public:
|
||||||
|
ScreenCapturerGdi();
|
||||||
|
~ScreenCapturerGdi();
|
||||||
|
|
||||||
|
public:
|
||||||
|
int Init(const int fps, cb_desktop_data cb) override;
|
||||||
|
int Destroy() override;
|
||||||
|
int Start(bool show_cursor) override;
|
||||||
|
int Stop() override;
|
||||||
|
|
||||||
|
int Pause(int monitor_index) override;
|
||||||
|
int Resume(int monitor_index) override;
|
||||||
|
|
||||||
|
int SwitchTo(int monitor_index) override;
|
||||||
|
|
||||||
|
std::vector<DisplayInfo> GetDisplayInfoList() override {
|
||||||
|
return display_info_list_;
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
static BOOL CALLBACK EnumMonitorProc(HMONITOR hMonitor, HDC, LPRECT,
|
||||||
|
LPARAM data);
|
||||||
|
void EnumerateDisplays();
|
||||||
|
void CaptureLoop();
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
|
std::atomic<bool> running_{false};
|
||||||
|
std::atomic<bool> paused_{false};
|
||||||
|
std::atomic<int> monitor_index_{0};
|
||||||
|
std::atomic<bool> show_cursor_{true};
|
||||||
|
std::thread thread_;
|
||||||
|
int fps_ = 60;
|
||||||
|
cb_desktop_data callback_ = nullptr;
|
||||||
|
|
||||||
|
unsigned char* nv12_frame_ = nullptr;
|
||||||
|
int nv12_width_ = 0;
|
||||||
|
int nv12_height_ = 0;
|
||||||
|
};
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
91
src/screen_capturer/windows/screen_capturer_win.cpp
Normal file
91
src/screen_capturer/windows/screen_capturer_win.cpp
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
#include "screen_capturer_win.h"
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
|
||||||
|
#include "rd_log.h"
|
||||||
|
#include "screen_capturer_dxgi.h"
|
||||||
|
#include "screen_capturer_gdi.h"
|
||||||
|
#include "screen_capturer_wgc.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
ScreenCapturerWin::ScreenCapturerWin() {}
|
||||||
|
ScreenCapturerWin::~ScreenCapturerWin() { Destroy(); }
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Init(const int fps, cb_desktop_data cb) {
|
||||||
|
fps_ = fps;
|
||||||
|
cb_ = cb;
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
impl_ = std::make_unique<ScreenCapturerWgc>();
|
||||||
|
ret = impl_->Init(fps_, cb_);
|
||||||
|
if (ret == 0) {
|
||||||
|
LOG_INFO("Windows capturer: using WGC");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARN("Windows capturer: WGC init failed (ret={}), try DXGI", ret);
|
||||||
|
impl_.reset();
|
||||||
|
|
||||||
|
impl_ = std::make_unique<ScreenCapturerDxgi>();
|
||||||
|
ret = impl_->Init(fps_, cb_);
|
||||||
|
if (ret == 0) {
|
||||||
|
LOG_INFO("Windows capturer: using DXGI Desktop Duplication");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_WARN("Windows capturer: DXGI init failed (ret={}), fallback to GDI", ret);
|
||||||
|
impl_.reset();
|
||||||
|
|
||||||
|
impl_ = std::make_unique<ScreenCapturerGdi>();
|
||||||
|
ret = impl_->Init(fps_, cb_);
|
||||||
|
if (ret == 0) {
|
||||||
|
LOG_INFO("Windows capturer: using GDI BitBlt");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR("Windows capturer: all implementations failed, ret={}", ret);
|
||||||
|
impl_.reset();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Destroy() {
|
||||||
|
if (impl_) {
|
||||||
|
impl_->Destroy();
|
||||||
|
impl_.reset();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Start(bool show_cursor) {
|
||||||
|
if (!impl_) return -1;
|
||||||
|
return impl_->Start(show_cursor);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Stop() {
|
||||||
|
if (!impl_) return 0;
|
||||||
|
return impl_->Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Pause(int monitor_index) {
|
||||||
|
if (!impl_) return -1;
|
||||||
|
return impl_->Pause(monitor_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Resume(int monitor_index) {
|
||||||
|
if (!impl_) return -1;
|
||||||
|
return impl_->Resume(monitor_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::SwitchTo(int monitor_index) {
|
||||||
|
if (!impl_) return -1;
|
||||||
|
return impl_->SwitchTo(monitor_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DisplayInfo> ScreenCapturerWin::GetDisplayInfoList() {
|
||||||
|
if (!impl_) return {};
|
||||||
|
return impl_->GetDisplayInfoList();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
42
src/screen_capturer/windows/screen_capturer_win.h
Normal file
42
src/screen_capturer/windows/screen_capturer_win.h
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-02-27
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _SCREEN_CAPTURER_WIN_H_
|
||||||
|
#define _SCREEN_CAPTURER_WIN_H_
|
||||||
|
|
||||||
|
#include <memory>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include "screen_capturer.h"
|
||||||
|
|
||||||
|
namespace crossdesk {
|
||||||
|
|
||||||
|
class ScreenCapturerWin : public ScreenCapturer {
|
||||||
|
public:
|
||||||
|
ScreenCapturerWin();
|
||||||
|
~ScreenCapturerWin();
|
||||||
|
|
||||||
|
public:
|
||||||
|
int Init(const int fps, cb_desktop_data cb) override;
|
||||||
|
int Destroy() override;
|
||||||
|
int Start(bool show_cursor) override;
|
||||||
|
int Stop() override;
|
||||||
|
|
||||||
|
int Pause(int monitor_index) override;
|
||||||
|
int Resume(int monitor_index) override;
|
||||||
|
|
||||||
|
int SwitchTo(int monitor_index) override;
|
||||||
|
|
||||||
|
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ScreenCapturer> impl_;
|
||||||
|
int fps_ = 60;
|
||||||
|
cb_desktop_data cb_;
|
||||||
|
};
|
||||||
|
} // namespace crossdesk
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -39,7 +39,7 @@ if is_os("windows") then
|
|||||||
add_requires("libyuv", "miniaudio 0.11.21")
|
add_requires("libyuv", "miniaudio 0.11.21")
|
||||||
add_links("Shell32", "windowsapp", "dwmapi", "User32", "kernel32",
|
add_links("Shell32", "windowsapp", "dwmapi", "User32", "kernel32",
|
||||||
"SDL3-static", "gdi32", "winmm", "setupapi", "version",
|
"SDL3-static", "gdi32", "winmm", "setupapi", "version",
|
||||||
"Imm32", "iphlpapi")
|
"Imm32", "iphlpapi", "d3d11", "dxgi")
|
||||||
add_cxflags("/WX")
|
add_cxflags("/WX")
|
||||||
set_runtimes("MT")
|
set_runtimes("MT")
|
||||||
elseif is_os("linux") then
|
elseif is_os("linux") then
|
||||||
|
|||||||
Reference in New Issue
Block a user