[feat] display selection supported on Windows platform

This commit is contained in:
dijunkun
2025-05-09 22:30:56 +08:00
parent c2da4ebcb7
commit 9b5023645c
10 changed files with 188 additions and 142 deletions

View File

@@ -13,7 +13,8 @@ typedef enum {
mouse = 0, mouse = 0,
keyboard, keyboard,
audio_capture, audio_capture,
host_infomation host_infomation,
display_id,
} ControlType; } ControlType;
typedef enum { typedef enum {
move = 0, move = 0,
@@ -51,6 +52,7 @@ typedef struct {
Key k; Key k;
HostInfo i; HostInfo i;
bool a; bool a;
int d;
}; };
} RemoteAction; } RemoteAction;

View File

@@ -32,8 +32,11 @@ class ScreenCapturer {
virtual int Destroy() = 0; virtual int Destroy() = 0;
virtual int Start() = 0; virtual int Start() = 0;
virtual int Stop() = 0; virtual int Stop() = 0;
virtual int Pause(int monitor_index) = 0;
virtual int Resume(int monitor_index) = 0;
virtual std::vector<DisplayInfo> GetDisplayList() = 0; virtual std::vector<DisplayInfo> GetDisplayList() = 0;
virtual int SwitchTo(int monitor_index) = 0;
}; };
#endif #endif

View File

@@ -28,19 +28,25 @@ BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
monitor_info_.cbSize = sizeof(MONITORINFOEX); monitor_info_.cbSize = sizeof(MONITORINFOEX);
if (GetMonitorInfo(hmonitor, &monitor_info_)) { if (GetMonitorInfo(hmonitor, &monitor_info_)) {
gs_display_list.push_back( if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice), gs_display_list.insert(
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false, gs_display_list.begin(),
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top, {(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom}); (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
*(HMONITOR *)data = hmonitor;
} else {
gs_display_list.push_back(
{(void *)hmonitor, WideToUtf8(monitor_info_.szDevice),
(monitor_info_.dwFlags & MONITORINFOF_PRIMARY) ? true : false,
monitor_info_.rcMonitor.left, monitor_info_.rcMonitor.top,
monitor_info_.rcMonitor.right, monitor_info_.rcMonitor.bottom});
}
} }
if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true; if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
if (monitor_info_.dwFlags & MONITORINFOF_PRIMARY) {
*(HMONITOR *)data = hmonitor;
}
return true; return true;
} }
@@ -84,164 +90,160 @@ bool ScreenCapturerWgc::IsWgcSupported() {
int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) { int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
int error = 0; int error = 0;
if (_inited == true) return error; if (inited_ == true) return error;
// nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2]; // nv12_frame_ = new unsigned char[rect.right * rect.bottom * 3 / 2];
// nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2]; // nv12_frame_scaled_ = new unsigned char[1280 * 720 * 3 / 2];
_fps = fps; fps_ = fps;
_on_data = cb; on_data_ = cb;
do { if (!IsWgcSupported()) {
if (!IsWgcSupported()) { LOG_ERROR("WGC not supported");
std::cout << "AE_UNSUPPORT" << std::endl; error = 2;
error = 2; return error;
break;
}
session_ = new WgcSessionImpl();
if (!session_) {
error = -1;
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
break;
}
session_->RegisterObserver(this);
monitor_ = GetPrimaryMonitor();
display_list_ = gs_display_list;
for (const auto &display : display_list_) {
LOG_INFO("Display Name: {}, Is Primary: {}, Bounds: ({}, {}) - ({}, {})",
display.name, (display.is_primary ? "Yes" : "No"), display.left,
display.top, display.right, display.bottom);
}
error = session_->Initialize(monitor_);
_inited = true;
} while (0);
if (error != 0) {
} }
return error; monitor_ = GetPrimaryMonitor();
display_list_ = gs_display_list;
if (display_list_.empty()) {
LOG_ERROR("No display found");
return -1;
}
for (int i = 0; i < display_list_.size(); i++) {
const auto &display = display_list_[i];
LOG_INFO(
"index: {}, display name: {}, is primary: {}, bounds: ({}, {}) - "
"({}, {})",
i, display.name, (display.is_primary ? "yes" : "no"), display.left,
display.top, display.right, display.bottom);
sessions_.push_back(
{std::make_unique<WgcSessionImpl>(), false, false, false});
sessions_.back().session_->RegisterObserver(this);
error = sessions_.back().session_->Initialize((HMONITOR)display.handle);
if (error != 0) {
return error;
}
sessions_[i].inited_ = true;
inited_ = true;
}
LOG_INFO("Default on monitor {}:{}", monitor_index_,
display_list_[monitor_index_].name);
return 0;
} }
int ScreenCapturerWgc::Destroy() { return 0; } int ScreenCapturerWgc::Destroy() { return 0; }
int ScreenCapturerWgc::Start() { int ScreenCapturerWgc::Start() {
if (_running == true) { if (running_ == true) {
std::cout << "record desktop duplication is already running" << std::endl; LOG_ERROR("Screen capturer already running");
return 0; return 0;
} }
if (_inited == false) { if (inited_ == false) {
std::cout << "AE_NEED_INIT" << std::endl; LOG_ERROR("Screen capturer not inited");
return 4; return 4;
} }
_running = true; for (int i = 0; i < sessions_.size(); i++) {
session_->Start(); if (sessions_[i].inited_ == false) {
LOG_ERROR("Session {} not inited", i);
continue;
}
if (sessions_[i].running_) {
LOG_ERROR("Session {} is already running", i);
} else {
sessions_[i].session_->Start();
if (i != 0) {
sessions_[i].session_->Pause();
sessions_[i].paused_ = true;
}
sessions_[i].running_ = true;
}
running_ = true;
}
return 0; return 0;
} }
int ScreenCapturerWgc::Pause() { int ScreenCapturerWgc::Pause(int monitor_index) {
_paused = true; if (monitor_index >= sessions_.size() || monitor_index < 0) {
if (session_) session_->Pause(); LOG_ERROR("Invalid session index: {}", monitor_index);
return -1;
}
if (!sessions_[monitor_index].paused_) {
sessions_[monitor_index].session_->Pause();
sessions_[monitor_index].paused_ = true;
LOG_INFO("Pausing session {}", monitor_index);
}
return 0; return 0;
} }
int ScreenCapturerWgc::Resume() { int ScreenCapturerWgc::Resume(int monitor_index) {
_paused = false; if (monitor_index >= sessions_.size() || monitor_index < 0) {
if (session_) session_->Resume(); LOG_ERROR("Invalid session index: {}", monitor_index);
return -1;
}
if (sessions_[monitor_index].paused_) {
sessions_[monitor_index].session_->Resume();
sessions_[monitor_index].paused_ = false;
LOG_INFO("Resuming session {}", monitor_index);
}
return 0; return 0;
} }
int ScreenCapturerWgc::Stop() { int ScreenCapturerWgc::Stop() {
_running = false; for (int i = 0; i < sessions_.size(); i++) {
if (sessions_[i].running_) {
if (session_) session_->Stop(); sessions_[i].session_->Stop();
sessions_[i].running_ = false;
}
}
return 0; return 0;
} }
int ScreenCapturerWgc::SwitchTo(int monitor_index) { int ScreenCapturerWgc::SwitchTo(int monitor_index) {
if (!_inited) return -1; if (monitor_index_ == monitor_index) {
LOG_INFO("Already on monitor {}:{}", monitor_index_,
display_list_[monitor_index_].name);
return 0;
}
if (monitor_index >= display_list_.size()) { if (monitor_index >= display_list_.size()) {
LOG_ERROR("Invalid monitor index: {}", monitor_index); LOG_ERROR("Invalid monitor index: {}", monitor_index);
return -3; return -1;
} }
LOG_INFO("Switching to monitor {}:{}", monitor_index, if (!sessions_[monitor_index].inited_) {
display_list_[monitor_index].name); LOG_ERROR("Monitor {} not inited", monitor_index);
return -1;
Stop();
if (session_) {
session_->Release();
delete session_;
session_ = nullptr;
} }
session_ = new WgcSessionImpl(); Pause(monitor_index_);
if (!session_) {
LOG_ERROR("Failed to create new WgcSessionImpl.");
return -4;
}
session_->RegisterObserver(this); monitor_index_ = monitor_index;
LOG_INFO("Switching to monitor {}:{}", monitor_index_,
display_list_[monitor_index_].name);
int err = session_->Initialize((HMONITOR)display_list_[monitor_index].handle); Resume(monitor_index);
if (err != 0) {
LOG_ERROR("Failed to re-initialize session on new monitor.");
return -5;
}
monitor_ = (HMONITOR)display_list_[monitor_index].handle; return 0;
_inited = true;
return Start();
}
void ConvertABGRtoBGRA(const uint8_t *abgr_data, uint8_t *bgra_data, int width,
int height, int abgr_stride, int bgra_stride) {
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
int abgr_index = (i * abgr_stride + j) * 4;
int bgra_index = (i * bgra_stride + j) * 4;
bgra_data[bgra_index + 0] = abgr_data[abgr_index + 2]; // 蓝色
bgra_data[bgra_index + 1] = abgr_data[abgr_index + 1]; // 绿色
bgra_data[bgra_index + 2] = abgr_data[abgr_index + 0]; // 红色
bgra_data[bgra_index + 3] = abgr_data[abgr_index + 3]; // Alpha
}
}
}
void ConvertBGRAtoABGR(const uint8_t *bgra_data, uint8_t *abgr_data, int width,
int height, int bgra_stride, int abgr_stride) {
for (int i = 0; i < height; ++i) {
for (int j = 0; j < width; ++j) {
int bgra_index = (i * bgra_stride + j) * 4;
int abgr_index = (i * abgr_stride + j) * 4;
abgr_data[abgr_index + 0] = bgra_data[bgra_index + 3]; // Alpha
abgr_data[abgr_index + 1] = bgra_data[bgra_index + 0]; // Blue
abgr_data[abgr_index + 2] = bgra_data[bgra_index + 1]; // Green
abgr_data[abgr_index + 3] = bgra_data[bgra_index + 2]; // Red
}
}
} }
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) { void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
if (_on_data) { if (on_data_) {
// int width = 1280;
// int height = 720;
if (!nv12_frame_) { if (!nv12_frame_) {
nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2]; nv12_frame_ = new unsigned char[frame.width * frame.height * 3 / 2];
} }
@@ -251,15 +253,20 @@ void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame &frame) {
(uint8_t *)(nv12_frame_ + frame.width * frame.height), (uint8_t *)(nv12_frame_ + frame.width * frame.height),
frame.width, frame.width, frame.height); frame.width, frame.width, frame.height);
_on_data(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width, on_data_(nv12_frame_, frame.width * frame.height * 3 / 2, frame.width,
frame.height); frame.height);
} }
} }
void ScreenCapturerWgc::CleanUp() { void ScreenCapturerWgc::CleanUp() {
_inited = false; if (inited_) {
for (auto &session : sessions_) {
if (session_) session_->Release(); if (session.session_) {
session.session_->Stop();
session_ = nullptr; session.session_->Release();
session.session_ = nullptr;
}
}
sessions_.clear();
}
} }

View File

@@ -25,8 +25,8 @@ class ScreenCapturerWgc : public ScreenCapturer,
virtual int Start() override; virtual int Start() override;
virtual int Stop() override; virtual int Stop() override;
int Pause(); virtual int Pause(int monitor_index) override;
int Resume(); virtual int Resume(int monitor_index) override;
std::vector<DisplayInfo> GetDisplayList() { return display_list_; } std::vector<DisplayInfo> GetDisplayList() { return display_list_; }
@@ -41,21 +41,25 @@ class ScreenCapturerWgc : public ScreenCapturer,
HMONITOR monitor_; HMONITOR monitor_;
MONITORINFOEX monitor_info_; MONITORINFOEX monitor_info_;
std::vector<DisplayInfo> display_list_; std::vector<DisplayInfo> display_list_;
int monitor_index_ = 0;
private: private:
WgcSession *session_ = nullptr; class WgcSessionInfo {
public:
std::unique_ptr<WgcSession> session_;
bool inited_ = false;
bool running_ = false;
bool paused_ = false;
};
std::atomic_bool _running; std::vector<WgcSessionInfo> sessions_;
std::atomic_bool _paused;
std::atomic_bool _inited;
std::thread _thread; std::atomic_bool running_;
std::atomic_bool inited_;
std::string _device_name; int fps_;
int _fps; cb_desktop_data on_data_ = nullptr;
cb_desktop_data _on_data = nullptr;
unsigned char *nv12_frame_ = nullptr; unsigned char *nv12_frame_ = nullptr;
unsigned char *nv12_frame_scaled_ = nullptr; unsigned char *nv12_frame_scaled_ = nullptr;

View File

@@ -124,6 +124,8 @@ int WgcSessionImpl::Stop() {
int WgcSessionImpl::Pause() { int WgcSessionImpl::Pause() {
std::lock_guard locker(lock_); std::lock_guard locker(lock_);
is_paused_ = true;
CHECK_INIT; CHECK_INIT;
return 0; return 0;
} }
@@ -131,6 +133,8 @@ int WgcSessionImpl::Pause() {
int WgcSessionImpl::Resume() { int WgcSessionImpl::Resume() {
std::lock_guard locker(lock_); std::lock_guard locker(lock_);
is_paused_ = false;
CHECK_INIT; CHECK_INIT;
return 0; return 0;
} }
@@ -233,6 +237,10 @@ void WgcSessionImpl::OnFrame(
// copy to mapped texture // copy to mapped texture
{ {
if (is_paused_) {
return;
}
auto frame_captured = auto frame_captured =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface()); GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());

View File

@@ -65,6 +65,14 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
for (int i = 0; i < display_list.size(); i++) { for (int i = 0; i < display_list.size(); i++) {
if (ImGui::Selectable(display_list[i].name.c_str())) { if (ImGui::Selectable(display_list[i].name.c_str())) {
selected_display_ = i + 1; selected_display_ = i + 1;
RemoteAction remote_action;
remote_action.type = ControlType::display_id;
remote_action.d = i;
if (props->connection_status_ == ConnectionStatus::Connected) {
SendDataFrame(props->peer_, (const char*)&remote_action,
sizeof(remote_action));
}
} }
} }
ImGui::SetWindowFontScale(1.0f); ImGui::SetWindowFontScale(1.0f);
@@ -118,7 +126,8 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
float disable_audio_x = ImGui::GetCursorScreenPos().x + 4; float disable_audio_x = ImGui::GetCursorScreenPos().x + 4;
float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f; float disable_audio_y = ImGui::GetCursorScreenPos().y + 4.0f;
// std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH // std::string audio = audio_capture_button_pressed_ ? ICON_FA_VOLUME_HIGH
// : ICON_FA_VOLUME_XMARK; // :
// ICON_FA_VOLUME_XMARK;
std::string audio = props->audio_capture_button_pressed_ std::string audio = props->audio_capture_button_pressed_
? ICON_FA_VOLUME_HIGH ? ICON_FA_VOLUME_HIGH
: ICON_FA_VOLUME_HIGH; : ICON_FA_VOLUME_HIGH;

View File

@@ -79,6 +79,8 @@ class Render {
int texture_height_ = 720; int texture_height_ = 720;
int video_width_ = 0; int video_width_ = 0;
int video_height_ = 0; int video_height_ = 0;
int video_width_last_ = 0;
int video_height_last_ = 0;
size_t video_size_ = 0; size_t video_size_ = 0;
bool tab_selected_ = false; bool tab_selected_ = false;
bool tab_opened_ = true; bool tab_opened_ = true;

View File

@@ -215,8 +215,11 @@ void Render::OnReceiveVideoBufferCb(const XVideoFrame *video_frame,
memcpy(props->dst_buffer_, video_frame->data, video_frame->size); memcpy(props->dst_buffer_, video_frame->data, video_frame->size);
bool need_to_update_render_rect = false; bool need_to_update_render_rect = false;
if (props->video_width_ == 0 && props->video_height_ == 0) { if (props->video_width_ != props->video_width_last_ ||
props->video_height_ != props->video_height_last_) {
need_to_update_render_rect = true; need_to_update_render_rect = true;
props->video_width_last_ = props->video_width_;
props->video_height_last_ = props->video_height_;
} }
props->video_width_ = video_frame->width; props->video_width_ = video_frame->width;
props->video_height_ = video_frame->height; props->video_height_ = video_frame->height;
@@ -261,6 +264,7 @@ void Render::OnReceiveDataBufferCb(const char *data, size_t size,
std::string remote_id(user_id, user_id_size); std::string remote_id(user_id, user_id_size);
if (render->client_properties_.find(remote_id) != if (render->client_properties_.find(remote_id) !=
render->client_properties_.end()) { render->client_properties_.end()) {
// local
auto props = render->client_properties_.find(remote_id)->second; auto props = render->client_properties_.find(remote_id)->second;
if (ControlType::host_infomation == remote_action.type) { if (ControlType::host_infomation == remote_action.type) {
props->remote_host_name_ = std::string(remote_action.i.host_name, props->remote_host_name_ = std::string(remote_action.i.host_name,
@@ -268,6 +272,7 @@ void Render::OnReceiveDataBufferCb(const char *data, size_t size,
LOG_INFO("Remote hostname: [{}]", props->remote_host_name_); LOG_INFO("Remote hostname: [{}]", props->remote_host_name_);
} }
} else { } else {
// remote
if (ControlType::mouse == remote_action.type && render->mouse_controller_) { if (ControlType::mouse == remote_action.type && render->mouse_controller_) {
render->mouse_controller_->SendMouseCommand(remote_action); render->mouse_controller_->SendMouseCommand(remote_action);
} else if (ControlType::audio_capture == remote_action.type) { } else if (ControlType::audio_capture == remote_action.type) {
@@ -283,6 +288,10 @@ void Render::OnReceiveDataBufferCb(const char *data, size_t size,
render->keyboard_capturer_->SendKeyboardCommand( render->keyboard_capturer_->SendKeyboardCommand(
(int)remote_action.k.key_value, (int)remote_action.k.key_value,
remote_action.k.flag == KeyFlag::key_down); remote_action.k.flag == KeyFlag::key_down);
} else if (ControlType::display_id == remote_action.type) {
if (render->screen_capturer_) {
render->screen_capturer_->SwitchTo(remote_action.d);
}
} }
} }
} }

View File

@@ -79,6 +79,7 @@ int Render::StreamWindow() {
props->render_window_y_ = pos.y; props->render_window_y_ = pos.y;
props->render_window_width_ = size.x; props->render_window_width_ = size.x;
props->render_window_height_ = size.y; props->render_window_height_ = size.y;
UpdateRenderRect();
ControlWindow(props); ControlWindow(props);
@@ -139,6 +140,7 @@ int Render::StreamWindow() {
props->render_window_y_ = pos.y; props->render_window_y_ = pos.y;
props->render_window_width_ = size.x; props->render_window_width_ = size.x;
props->render_window_height_ = size.y; props->render_window_height_ = size.y;
UpdateRenderRect();
ControlWindow(props); ControlWindow(props);
ImGui::End(); ImGui::End();
@@ -161,7 +163,7 @@ int Render::StreamWindow() {
} }
} }
UpdateRenderRect(); // UpdateRenderRect();
ImGui::End(); // End VideoBg ImGui::End(); // End VideoBg
return 0; return 0;