mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-03-22 15:59:07 +08:00
Compare commits
28 Commits
a94da8802f
...
v1.3.0-202
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b230b851e4 | ||
|
|
ff32477ffe | ||
|
|
c6c60decdb | ||
|
|
7505adeca8 | ||
|
|
754f1fba88 | ||
|
|
8be46b870a | ||
|
|
81cb1d6c0b | ||
|
|
319416f1b7 | ||
|
|
d679c6251b | ||
|
|
a14baafda7 | ||
|
|
cfdc7d3106 | ||
|
|
33d51b8ce5 | ||
|
|
b13dac2093 | ||
|
|
a605c95e5a | ||
|
|
9a5553a636 | ||
|
|
ef02403da6 | ||
|
|
adfab363c1 | ||
|
|
123d4cf595 | ||
|
|
19feb8ff49 | ||
|
|
9223bf9d2d | ||
|
|
11b5f87841 | ||
|
|
cea59fb453 | ||
|
|
3a179bf480 | ||
|
|
b9c53024f1 | ||
|
|
62b37ad698 | ||
|
|
de56cd5d3b | ||
|
|
8d9d78185a | ||
|
|
b10a6512fe |
@@ -138,6 +138,39 @@ productbuild \
|
|||||||
|
|
||||||
echo "PKG package created: ${PKG_NAME}"
|
echo "PKG package created: ${PKG_NAME}"
|
||||||
|
|
||||||
|
# Set custom icon for PKG file
|
||||||
|
if [ -f "${ICON_PATH}" ]; then
|
||||||
|
echo "Setting custom icon for PKG file..."
|
||||||
|
# Create a temporary iconset from icns
|
||||||
|
TEMP_ICON_DIR=$(mktemp -d)
|
||||||
|
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
|
||||||
|
|
||||||
|
# Use sips to create a png from icns for the icon
|
||||||
|
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Method: Use osascript to set file icon (works on macOS)
|
||||||
|
osascript <<APPLESCRIPT
|
||||||
|
use framework "Foundation"
|
||||||
|
use framework "AppKit"
|
||||||
|
|
||||||
|
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
|
||||||
|
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
|
||||||
|
|
||||||
|
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
|
||||||
|
set workspace to current application's NSWorkspace's sharedWorkspace()
|
||||||
|
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
|
||||||
|
APPLESCRIPT
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Custom icon set successfully for ${PKG_NAME}"
|
||||||
|
else
|
||||||
|
echo "Warning: Failed to set custom icon (this is optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "${TEMP_ICON_DIR}"
|
||||||
|
fi
|
||||||
|
echo "Set icon finished"
|
||||||
|
|
||||||
rm -rf build_pkg_temp build_pkg_scripts ${APP_BUNDLE}
|
rm -rf build_pkg_temp build_pkg_scripts ${APP_BUNDLE}
|
||||||
|
|
||||||
echo "PKG package created successfully."
|
echo "PKG package created successfully."
|
||||||
|
|||||||
@@ -138,6 +138,39 @@ productbuild \
|
|||||||
|
|
||||||
echo "PKG package created: ${PKG_NAME}"
|
echo "PKG package created: ${PKG_NAME}"
|
||||||
|
|
||||||
|
# Set custom icon for PKG file
|
||||||
|
if [ -f "${ICON_PATH}" ]; then
|
||||||
|
echo "Setting custom icon for PKG file..."
|
||||||
|
# Create a temporary iconset from icns
|
||||||
|
TEMP_ICON_DIR=$(mktemp -d)
|
||||||
|
cp "${ICON_PATH}" "${TEMP_ICON_DIR}/icon.icns"
|
||||||
|
|
||||||
|
# Use sips to create a png from icns for the icon
|
||||||
|
sips -s format png "${TEMP_ICON_DIR}/icon.icns" --out "${TEMP_ICON_DIR}/icon.png" 2>/dev/null || true
|
||||||
|
|
||||||
|
# Method: Use osascript to set file icon (works on macOS)
|
||||||
|
osascript <<APPLESCRIPT
|
||||||
|
use framework "Foundation"
|
||||||
|
use framework "AppKit"
|
||||||
|
|
||||||
|
set iconPath to POSIX file "${TEMP_ICON_DIR}/icon.icns"
|
||||||
|
set targetPath to POSIX file "$(pwd)/${PKG_NAME}"
|
||||||
|
|
||||||
|
set iconImage to current application's NSImage's alloc()'s initWithContentsOfFile:(POSIX path of iconPath)
|
||||||
|
set workspace to current application's NSWorkspace's sharedWorkspace()
|
||||||
|
workspace's setIcon:iconImage forFile:(POSIX path of targetPath) options:0
|
||||||
|
APPLESCRIPT
|
||||||
|
|
||||||
|
if [ $? -eq 0 ]; then
|
||||||
|
echo "Custom icon set successfully for ${PKG_NAME}"
|
||||||
|
else
|
||||||
|
echo "Warning: Failed to set custom icon (this is optional)"
|
||||||
|
fi
|
||||||
|
|
||||||
|
rm -rf "${TEMP_ICON_DIR}"
|
||||||
|
fi
|
||||||
|
echo "Set icon finished"
|
||||||
|
|
||||||
rm -rf build_pkg_temp build_pkg_scripts ${APP_BUNDLE}
|
rm -rf build_pkg_temp build_pkg_scripts ${APP_BUNDLE}
|
||||||
|
|
||||||
echo "PKG package created successfully."
|
echo "PKG package created successfully."
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ class ConfigCenter {
|
|||||||
const char* section_ = "Settings";
|
const char* section_ = "Settings";
|
||||||
|
|
||||||
LANGUAGE language_ = LANGUAGE::CHINESE;
|
LANGUAGE language_ = LANGUAGE::CHINESE;
|
||||||
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::MEDIUM;
|
VIDEO_QUALITY video_quality_ = VIDEO_QUALITY::HIGH;
|
||||||
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_60;
|
VIDEO_FRAME_RATE video_frame_rate_ = VIDEO_FRAME_RATE::FPS_60;
|
||||||
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
|
VIDEO_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
|
||||||
bool hardware_video_codec_ = false;
|
bool hardware_video_codec_ = false;
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -146,6 +146,8 @@ static std::vector<std::string> p2p_disconnected = {
|
|||||||
static std::vector<std::string> p2p_connecting = {
|
static std::vector<std::string> p2p_connecting = {
|
||||||
reinterpret_cast<const char*>(u8"正在建立对等连接..."),
|
reinterpret_cast<const char*>(u8"正在建立对等连接..."),
|
||||||
"P2P Connecting ..."};
|
"P2P Connecting ..."};
|
||||||
|
static std::vector<std::string> receiving_screen = {
|
||||||
|
reinterpret_cast<const char*>(u8"画面接收中..."), "Receiving screen..."};
|
||||||
static std::vector<std::string> p2p_failed = {
|
static std::vector<std::string> p2p_failed = {
|
||||||
reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"};
|
reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"};
|
||||||
static std::vector<std::string> p2p_closed = {
|
static std::vector<std::string> p2p_closed = {
|
||||||
@@ -216,6 +218,13 @@ static std::vector<std::string> connection_mode_direct = {
|
|||||||
reinterpret_cast<const char*>(u8"直连"), "Direct"};
|
reinterpret_cast<const char*>(u8"直连"), "Direct"};
|
||||||
static std::vector<std::string> connection_mode_relay = {
|
static std::vector<std::string> connection_mode_relay = {
|
||||||
reinterpret_cast<const char*>(u8"中继"), "Relay"};
|
reinterpret_cast<const char*>(u8"中继"), "Relay"};
|
||||||
|
static std::vector<std::string> online = {
|
||||||
|
reinterpret_cast<const char*>(u8"在线"), "Online"};
|
||||||
|
static std::vector<std::string> offline = {
|
||||||
|
reinterpret_cast<const char*>(u8"离线"), "Offline"};
|
||||||
|
static std::vector<std::string> device_offline = {
|
||||||
|
reinterpret_cast<const char*>(u8"设备离线"), "Device Offline"};
|
||||||
|
|
||||||
#if _WIN32
|
#if _WIN32
|
||||||
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
|
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
36
src/gui/device_presence.h
Normal file
36
src/gui/device_presence.h
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/*
|
||||||
|
* @Author: DI JUNKUN
|
||||||
|
* @Date: 2026-02-28
|
||||||
|
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _DEVICE_PRESENCE_H_
|
||||||
|
#define _DEVICE_PRESENCE_H_
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
class DevicePresence {
|
||||||
|
public:
|
||||||
|
void SetOnline(const std::string& device_id, bool online) {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
cache_[device_id] = online;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool IsOnline(const std::string& device_id) const {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
return cache_.count(device_id) > 0 && cache_.at(device_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Clear() {
|
||||||
|
std::lock_guard<std::mutex> lock(mutex_);
|
||||||
|
cache_.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unordered_map<std::string, bool> cache_;
|
||||||
|
mutable std::mutex mutex_;
|
||||||
|
};
|
||||||
|
|
||||||
|
#endif
|
||||||
@@ -17,7 +17,7 @@ int Render::LocalWindow() {
|
|||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(0.0f, io.DisplaySize.y * TITLE_BAR_HEIGHT),
|
ImGui::SetNextWindowPos(ImVec2(0.0f, io.DisplaySize.y * TITLE_BAR_HEIGHT),
|
||||||
ImGuiCond_Always);
|
ImGuiCond_Always);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
|
||||||
ImGui::BeginChild("LocalDesktopWindow",
|
ImGui::BeginChild("LocalDesktopWindow",
|
||||||
@@ -42,7 +42,7 @@ int Render::LocalWindow() {
|
|||||||
ImGuiCond_Always);
|
ImGuiCond_Always);
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255,
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255,
|
||||||
242.0f / 255, 1.0f));
|
242.0f / 255, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, window_rounding_ * 1.5f);
|
||||||
ImGui::BeginChild(
|
ImGui::BeginChild(
|
||||||
"LocalDesktopPanel",
|
"LocalDesktopPanel",
|
||||||
ImVec2(local_window_width * 0.8f, local_window_height * 0.43f),
|
ImVec2(local_window_width * 0.8f, local_window_height * 0.43f),
|
||||||
@@ -101,7 +101,7 @@ int Render::LocalWindow() {
|
|||||||
ImGuiCol_WindowBg,
|
ImGuiCol_WindowBg,
|
||||||
ImVec4(1.0f, 1.0f, 1.0f, 1.0f - (float)time_duration));
|
ImVec4(1.0f, 1.0f, 1.0f, 1.0f - (float)time_duration));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
||||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||||
ImGuiWindowFlags_NoSavedSettings);
|
ImGuiWindowFlags_NoSavedSettings);
|
||||||
@@ -177,10 +177,11 @@ int Render::LocalWindow() {
|
|||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding,
|
||||||
|
window_rounding_ * 0.5f);
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
|
|
||||||
ImGui::Begin("ResetPasswordWindow", nullptr,
|
ImGui::Begin("ResetPasswordWindow", nullptr,
|
||||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||||
|
|||||||
@@ -122,6 +122,8 @@ int Render::ShowRecentConnections() {
|
|||||||
it.second.remote_host_name = "unknown";
|
it.second.remote_host_name = "unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool online = device_presence_.IsOnline(it.second.remote_id);
|
||||||
|
|
||||||
ImVec2 image_screen_pos = ImVec2(
|
ImVec2 image_screen_pos = ImVec2(
|
||||||
ImGui::GetCursorScreenPos().x + recent_connection_image_width * 0.04f,
|
ImGui::GetCursorScreenPos().x + recent_connection_image_width * 0.04f,
|
||||||
ImGui::GetCursorScreenPos().y + recent_connection_image_height * 0.08f);
|
ImGui::GetCursorScreenPos().y + recent_connection_image_height * 0.08f);
|
||||||
@@ -132,6 +134,29 @@ int Render::ShowRecentConnections() {
|
|||||||
ImGui::Image(
|
ImGui::Image(
|
||||||
(ImTextureID)(intptr_t)it.second.texture,
|
(ImTextureID)(intptr_t)it.second.texture,
|
||||||
ImVec2(recent_connection_image_width, recent_connection_image_height));
|
ImVec2(recent_connection_image_width, recent_connection_image_height));
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
|
std::string display_host_name_with_presence =
|
||||||
|
it.second.remote_host_name + " " +
|
||||||
|
(online ? localization::online[localization_language_index_]
|
||||||
|
: localization::offline[localization_language_index_]);
|
||||||
|
ImGui::Text("%s", display_host_name_with_presence.c_str());
|
||||||
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
|
||||||
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
ImVec2 circle_pos =
|
||||||
|
ImVec2(image_screen_pos.x + recent_connection_image_width * 0.07f,
|
||||||
|
image_screen_pos.y + recent_connection_image_height * 0.12f);
|
||||||
|
ImU32 fill_color =
|
||||||
|
online ? IM_COL32(0, 255, 0, 255) : IM_COL32(140, 140, 140, 255);
|
||||||
|
ImU32 border_color = IM_COL32(255, 255, 255, 255);
|
||||||
|
float dot_radius = recent_connection_image_height * 0.06f;
|
||||||
|
draw_list->AddCircleFilled(circle_pos, dot_radius * 1.25f, border_color,
|
||||||
|
100);
|
||||||
|
draw_list->AddCircleFilled(circle_pos, dot_radius, fill_color, 100);
|
||||||
|
|
||||||
// remote id display button
|
// remote id display button
|
||||||
{
|
{
|
||||||
@@ -155,14 +180,6 @@ int Render::ShowRecentConnections() {
|
|||||||
ImGui::Text("%s", it.second.remote_id.c_str());
|
ImGui::Text("%s", it.second.remote_id.c_str());
|
||||||
ImGui::SetWindowFontScale(1.0f);
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
ImGui::PopStyleColor(3);
|
ImGui::PopStyleColor(3);
|
||||||
|
|
||||||
if (ImGui::IsItemHovered()) {
|
|
||||||
ImGui::BeginTooltip();
|
|
||||||
ImGui::SetWindowFontScale(0.5f);
|
|
||||||
ImGui::Text("%s", it.second.remote_host_name.c_str());
|
|
||||||
ImGui::SetWindowFontScale(1.0f);
|
|
||||||
ImGui::EndTooltip();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f));
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0.2f));
|
||||||
@@ -242,6 +259,9 @@ int Render::ShowRecentConnections() {
|
|||||||
if (show_confirm_delete_connection_) {
|
if (show_confirm_delete_connection_) {
|
||||||
ConfirmDeleteConnection();
|
ConfirmDeleteConnection();
|
||||||
}
|
}
|
||||||
|
if (show_offline_warning_window_) {
|
||||||
|
OfflineWarningWindow();
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -253,10 +273,10 @@ int Render::ConfirmDeleteConnection() {
|
|||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
|
|
||||||
ImGui::Begin("ConfirmDeleteConnectionWindow", nullptr,
|
ImGui::Begin("ConfirmDeleteConnectionWindow", nullptr,
|
||||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||||
@@ -299,4 +319,45 @@ int Render::ConfirmDeleteConnection() {
|
|||||||
ImGui::PopStyleVar();
|
ImGui::PopStyleVar();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Render::OfflineWarningWindow() {
|
||||||
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
|
ImGui::SetNextWindowPos(
|
||||||
|
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
||||||
|
ImGui::SetNextWindowSize(
|
||||||
|
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
||||||
|
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
|
|
||||||
|
ImGui::Begin("OfflineWarningWindow", nullptr,
|
||||||
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||||
|
ImGuiWindowFlags_NoSavedSettings);
|
||||||
|
ImGui::PopStyleVar(2);
|
||||||
|
ImGui::PopStyleColor();
|
||||||
|
|
||||||
|
auto window_width = ImGui::GetWindowSize().x;
|
||||||
|
auto window_height = ImGui::GetWindowSize().y;
|
||||||
|
|
||||||
|
ImGui::SetCursorPosX(window_width * 0.43f);
|
||||||
|
ImGui::SetCursorPosY(window_height * 0.67f);
|
||||||
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
|
if (ImGui::Button(localization::ok[localization_language_index_].c_str()) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_Enter) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||||
|
show_offline_warning_window_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto text_width = ImGui::CalcTextSize(offline_warning_text_.c_str()).x;
|
||||||
|
ImGui::SetCursorPosX((window_width - text_width) * 0.5f);
|
||||||
|
ImGui::SetCursorPosY(window_height * 0.2f);
|
||||||
|
ImGui::Text("%s", offline_warning_text_.c_str());
|
||||||
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
|
|
||||||
|
ImGui::End();
|
||||||
|
ImGui::PopStyleVar();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
@@ -18,7 +18,7 @@ int Render::RemoteWindow() {
|
|||||||
ImGui::SetNextWindowPos(
|
ImGui::SetNextWindowPos(
|
||||||
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * TITLE_BAR_HEIGHT),
|
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * TITLE_BAR_HEIGHT),
|
||||||
ImGuiCond_Always);
|
ImGuiCond_Always);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(0, 0, 0, 0));
|
||||||
ImGui::BeginChild("RemoteDesktopWindow",
|
ImGui::BeginChild("RemoteDesktopWindow",
|
||||||
@@ -197,6 +197,9 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
|
|||||||
props->control_window_max_width_ = title_bar_height_ * 9.0f;
|
props->control_window_max_width_ = title_bar_height_ * 9.0f;
|
||||||
props->control_window_max_height_ = title_bar_height_ * 7.0f;
|
props->control_window_max_height_ = title_bar_height_ * 7.0f;
|
||||||
|
|
||||||
|
props->connection_status_ = ConnectionStatus::Connecting;
|
||||||
|
show_connection_status_window_ = true;
|
||||||
|
|
||||||
if (!props->peer_) {
|
if (!props->peer_) {
|
||||||
LOG_INFO("Create peer [{}] instance failed", props->local_id_);
|
LOG_INFO("Create peer [{}] instance failed", props->local_id_);
|
||||||
return -1;
|
return -1;
|
||||||
|
|||||||
@@ -810,6 +810,7 @@ int Render::CreateConnectionPeer() {
|
|||||||
params_.on_receive_video_frame = OnReceiveVideoBufferCb;
|
params_.on_receive_video_frame = OnReceiveVideoBufferCb;
|
||||||
|
|
||||||
params_.on_signal_status = OnSignalStatusCb;
|
params_.on_signal_status = OnSignalStatusCb;
|
||||||
|
params_.on_signal_message = OnSignalMessageCb;
|
||||||
params_.on_connection_status = OnConnectionStatusCb;
|
params_.on_connection_status = OnConnectionStatusCb;
|
||||||
params_.on_net_status_report = OnNetStatusReport;
|
params_.on_net_status_report = OnNetStatusReport;
|
||||||
|
|
||||||
@@ -937,6 +938,7 @@ int Render::CreateMainWindow() {
|
|||||||
(int)(server_window_width_default_ * dpi_scale_);
|
(int)(server_window_width_default_ * dpi_scale_);
|
||||||
server_window_normal_height_ =
|
server_window_normal_height_ =
|
||||||
(int)(server_window_height_default_ * dpi_scale_);
|
(int)(server_window_height_default_ * dpi_scale_);
|
||||||
|
window_rounding_ = window_rounding_default_ * dpi_scale_;
|
||||||
|
|
||||||
SDL_SetWindowSize(main_window_, (int)main_window_width_,
|
SDL_SetWindowSize(main_window_, (int)main_window_width_,
|
||||||
(int)main_window_height_);
|
(int)main_window_height_);
|
||||||
@@ -1058,13 +1060,18 @@ int Render::DestroyStreamWindow() {
|
|||||||
|
|
||||||
if (stream_renderer_) {
|
if (stream_renderer_) {
|
||||||
SDL_DestroyRenderer(stream_renderer_);
|
SDL_DestroyRenderer(stream_renderer_);
|
||||||
|
stream_renderer_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (stream_window_) {
|
if (stream_window_) {
|
||||||
SDL_DestroyWindow(stream_window_);
|
SDL_DestroyWindow(stream_window_);
|
||||||
|
stream_window_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
stream_window_created_ = false;
|
stream_window_created_ = false;
|
||||||
|
focus_on_stream_window_ = false;
|
||||||
|
stream_window_grabbed_ = false;
|
||||||
|
control_mouse_ = false;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1166,13 +1173,16 @@ int Render::DestroyServerWindow() {
|
|||||||
|
|
||||||
if (server_renderer_) {
|
if (server_renderer_) {
|
||||||
SDL_DestroyRenderer(server_renderer_);
|
SDL_DestroyRenderer(server_renderer_);
|
||||||
|
server_renderer_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (server_window_) {
|
if (server_window_) {
|
||||||
SDL_DestroyWindow(server_window_);
|
SDL_DestroyWindow(server_window_);
|
||||||
|
server_window_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
server_window_created_ = false;
|
server_window_created_ = false;
|
||||||
|
server_window_inited_ = false;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@@ -1296,7 +1306,7 @@ int Render::DrawMainWindow() {
|
|||||||
|
|
||||||
ImGuiIO& io = ImGui::GetIO();
|
ImGuiIO& io = ImGui::GetIO();
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
|
|
||||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||||
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y),
|
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y),
|
||||||
@@ -1539,7 +1549,8 @@ void Render::InitializeModules() {
|
|||||||
std::shared_lock lock(client_properties_mutex_);
|
std::shared_lock lock(client_properties_mutex_);
|
||||||
int ret = -1;
|
int ret = -1;
|
||||||
for (const auto& [remote_id, props] : client_properties_) {
|
for (const auto& [remote_id, props] : client_properties_) {
|
||||||
if (props && props->peer_ && props->connection_established_) {
|
if (props && props->peer_ && props->connection_established_ &&
|
||||||
|
props->enable_mouse_control_) {
|
||||||
ret = SendReliableDataFrame(props->peer_, data, size,
|
ret = SendReliableDataFrame(props->peer_, data, size,
|
||||||
props->clipboard_label_.c_str());
|
props->clipboard_label_.c_str());
|
||||||
if (ret != 0) {
|
if (ret != 0) {
|
||||||
@@ -1593,6 +1604,7 @@ void Render::MainLoop() {
|
|||||||
|
|
||||||
UpdateLabels();
|
UpdateLabels();
|
||||||
HandleRecentConnections();
|
HandleRecentConnections();
|
||||||
|
HandleConnectionStatusChange();
|
||||||
HandleStreamWindow();
|
HandleStreamWindow();
|
||||||
HandleServerWindow();
|
HandleServerWindow();
|
||||||
|
|
||||||
@@ -1632,10 +1644,46 @@ void Render::HandleRecentConnections() {
|
|||||||
LOG_INFO("Load recent connection thumbnails");
|
LOG_INFO("Load recent connection thumbnails");
|
||||||
}
|
}
|
||||||
reload_recent_connections_ = false;
|
reload_recent_connections_ = false;
|
||||||
|
|
||||||
|
recent_connection_ids_.clear();
|
||||||
|
for (const auto& conn : recent_connections_) {
|
||||||
|
recent_connection_ids_.push_back(conn.first);
|
||||||
|
}
|
||||||
|
need_to_send_recent_connections_ = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Render::HandleConnectionStatusChange() {
|
||||||
|
if (signal_connected_ && peer_ && need_to_send_recent_connections_) {
|
||||||
|
if (!recent_connection_ids_.empty()) {
|
||||||
|
nlohmann::json j;
|
||||||
|
j["type"] = "recent_connections_presence";
|
||||||
|
j["user_id"] = client_id_;
|
||||||
|
j["devices"] = nlohmann::json::array();
|
||||||
|
for (const auto& id : recent_connection_ids_) {
|
||||||
|
std::string pure_id = id;
|
||||||
|
size_t pos_y = pure_id.find('Y');
|
||||||
|
size_t pos_n = pure_id.find('N');
|
||||||
|
size_t pos = std::string::npos;
|
||||||
|
if (pos_y != std::string::npos &&
|
||||||
|
(pos_n == std::string::npos || pos_y < pos_n)) {
|
||||||
|
pos = pos_y;
|
||||||
|
} else if (pos_n != std::string::npos) {
|
||||||
|
pos = pos_n;
|
||||||
|
}
|
||||||
|
if (pos != std::string::npos) {
|
||||||
|
pure_id = pure_id.substr(0, pos);
|
||||||
|
}
|
||||||
|
j["devices"].push_back(pure_id);
|
||||||
|
}
|
||||||
|
auto s = j.dump();
|
||||||
|
SendSignalMessage(peer_, s.data(), s.size());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
need_to_send_recent_connections_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
void Render::HandleStreamWindow() {
|
void Render::HandleStreamWindow() {
|
||||||
if (need_to_create_stream_window_) {
|
if (need_to_create_stream_window_) {
|
||||||
CreateStreamWindow();
|
CreateStreamWindow();
|
||||||
|
|||||||
@@ -25,6 +25,7 @@
|
|||||||
#include "IconsFontAwesome6.h"
|
#include "IconsFontAwesome6.h"
|
||||||
#include "config_center.h"
|
#include "config_center.h"
|
||||||
#include "device_controller_factory.h"
|
#include "device_controller_factory.h"
|
||||||
|
#include "device_presence.h"
|
||||||
#include "imgui.h"
|
#include "imgui.h"
|
||||||
#include "imgui_impl_sdl3.h"
|
#include "imgui_impl_sdl3.h"
|
||||||
#include "imgui_impl_sdlrenderer3.h"
|
#include "imgui_impl_sdlrenderer3.h"
|
||||||
@@ -94,7 +95,7 @@ class Render {
|
|||||||
bool connection_established_ = false;
|
bool connection_established_ = false;
|
||||||
bool rejoin_ = false;
|
bool rejoin_ = false;
|
||||||
bool net_traffic_stats_button_pressed_ = false;
|
bool net_traffic_stats_button_pressed_ = false;
|
||||||
bool mouse_control_button_pressed_ = true;
|
bool enable_mouse_control_ = true;
|
||||||
bool mouse_controller_is_started_ = false;
|
bool mouse_controller_is_started_ = false;
|
||||||
bool audio_capture_button_pressed_ = true;
|
bool audio_capture_button_pressed_ = true;
|
||||||
bool control_mouse_ = true;
|
bool control_mouse_ = true;
|
||||||
@@ -192,6 +193,7 @@ class Render {
|
|||||||
void UpdateLabels();
|
void UpdateLabels();
|
||||||
void UpdateInteractions();
|
void UpdateInteractions();
|
||||||
void HandleRecentConnections();
|
void HandleRecentConnections();
|
||||||
|
void HandleConnectionStatusChange();
|
||||||
void HandleStreamWindow();
|
void HandleStreamWindow();
|
||||||
void HandleServerWindow();
|
void HandleServerWindow();
|
||||||
void Cleanup();
|
void Cleanup();
|
||||||
@@ -255,9 +257,12 @@ class Render {
|
|||||||
int DrawStreamWindow();
|
int DrawStreamWindow();
|
||||||
int DrawServerWindow();
|
int DrawServerWindow();
|
||||||
int ConfirmDeleteConnection();
|
int ConfirmDeleteConnection();
|
||||||
|
int OfflineWarningWindow();
|
||||||
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
|
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
|
||||||
void DrawConnectionStatusText(
|
void DrawConnectionStatusText(
|
||||||
std::shared_ptr<SubStreamWindowProperties>& props);
|
std::shared_ptr<SubStreamWindowProperties>& props);
|
||||||
|
void DrawReceivingScreenText(
|
||||||
|
std::shared_ptr<SubStreamWindowProperties>& props);
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
int RequestPermissionWindow();
|
int RequestPermissionWindow();
|
||||||
bool CheckScreenRecordingPermission();
|
bool CheckScreenRecordingPermission();
|
||||||
@@ -286,6 +291,9 @@ class Render {
|
|||||||
static void OnSignalStatusCb(SignalStatus status, const char* user_id,
|
static void OnSignalStatusCb(SignalStatus status, const char* user_id,
|
||||||
size_t user_id_size, void* user_data);
|
size_t user_id_size, void* user_data);
|
||||||
|
|
||||||
|
static void OnSignalMessageCb(const char* message, size_t size,
|
||||||
|
void* user_data);
|
||||||
|
|
||||||
static void OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
static void OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
||||||
size_t user_id_size, void* user_data);
|
size_t user_id_size, void* user_data);
|
||||||
|
|
||||||
@@ -402,9 +410,12 @@ class Render {
|
|||||||
// recent connections
|
// recent connections
|
||||||
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>
|
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>
|
||||||
recent_connections_;
|
recent_connections_;
|
||||||
|
std::vector<std::string> recent_connection_ids_;
|
||||||
int recent_connection_image_width_ = 160;
|
int recent_connection_image_width_ = 160;
|
||||||
int recent_connection_image_height_ = 90;
|
int recent_connection_image_height_ = 90;
|
||||||
uint32_t recent_connection_image_save_time_ = 0;
|
uint32_t recent_connection_image_save_time_ = 0;
|
||||||
|
DevicePresence device_presence_;
|
||||||
|
bool need_to_send_recent_connections_ = true;
|
||||||
|
|
||||||
// main window render
|
// main window render
|
||||||
SDL_Window* main_window_ = nullptr;
|
SDL_Window* main_window_ = nullptr;
|
||||||
@@ -430,7 +441,7 @@ class Render {
|
|||||||
bool screen_capturer_is_started_ = false;
|
bool screen_capturer_is_started_ = false;
|
||||||
bool start_speaker_capturer_ = false;
|
bool start_speaker_capturer_ = false;
|
||||||
bool speaker_capturer_is_started_ = false;
|
bool speaker_capturer_is_started_ = false;
|
||||||
bool start_keyboard_capturer_ = true;
|
bool start_keyboard_capturer_ = false;
|
||||||
bool show_cursor_ = false;
|
bool show_cursor_ = false;
|
||||||
bool keyboard_capturer_is_started_ = false;
|
bool keyboard_capturer_is_started_ = false;
|
||||||
bool foucs_on_main_window_ = false;
|
bool foucs_on_main_window_ = false;
|
||||||
@@ -534,6 +545,8 @@ class Render {
|
|||||||
int server_window_normal_height_ = 150;
|
int server_window_normal_height_ = 150;
|
||||||
float server_window_dpi_scaling_w_ = 1.0f;
|
float server_window_dpi_scaling_w_ = 1.0f;
|
||||||
float server_window_dpi_scaling_h_ = 1.0f;
|
float server_window_dpi_scaling_h_ = 1.0f;
|
||||||
|
float window_rounding_ = 6.0f;
|
||||||
|
float window_rounding_default_ = 6.0f;
|
||||||
|
|
||||||
// server window collapsed mode
|
// server window collapsed mode
|
||||||
bool server_window_collapsed_ = false;
|
bool server_window_collapsed_ = false;
|
||||||
@@ -569,9 +582,11 @@ class Render {
|
|||||||
bool is_server_mode_ = false;
|
bool is_server_mode_ = false;
|
||||||
bool reload_recent_connections_ = true;
|
bool reload_recent_connections_ = true;
|
||||||
bool show_confirm_delete_connection_ = false;
|
bool show_confirm_delete_connection_ = false;
|
||||||
|
bool show_offline_warning_window_ = false;
|
||||||
bool delete_connection_ = false;
|
bool delete_connection_ = false;
|
||||||
bool is_tab_bar_hovered_ = false;
|
bool is_tab_bar_hovered_ = false;
|
||||||
std::string delete_connection_name_ = "";
|
std::string delete_connection_name_ = "";
|
||||||
|
std::string offline_warning_text_ = "";
|
||||||
bool re_enter_remote_id_ = false;
|
bool re_enter_remote_id_ = false;
|
||||||
double copy_start_time_ = 0;
|
double copy_start_time_ = 0;
|
||||||
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
SignalStatus signal_status_ = SignalStatus::SignalClosed;
|
||||||
@@ -622,10 +637,10 @@ class Render {
|
|||||||
char self_hosted_id_[17] = "";
|
char self_hosted_id_[17] = "";
|
||||||
char self_hosted_user_id_[17] = "";
|
char self_hosted_user_id_[17] = "";
|
||||||
int language_button_value_ = 0;
|
int language_button_value_ = 0;
|
||||||
int video_quality_button_value_ = 0;
|
int video_quality_button_value_ = 2;
|
||||||
int video_frame_rate_button_value_ = 1;
|
int video_frame_rate_button_value_ = 1;
|
||||||
int video_encode_format_button_value_ = 0;
|
int video_encode_format_button_value_ = 0;
|
||||||
bool enable_hardware_video_codec_ = false;
|
bool enable_hardware_video_codec_ = true;
|
||||||
bool enable_turn_ = true;
|
bool enable_turn_ = true;
|
||||||
bool enable_srtp_ = false;
|
bool enable_srtp_ = false;
|
||||||
char signal_server_ip_[256] = "api.crossdesk.cn";
|
char signal_server_ip_[256] = "api.crossdesk.cn";
|
||||||
@@ -677,4 +692,4 @@ class Render {
|
|||||||
FileTransferState file_transfer_;
|
FileTransferState file_transfer_;
|
||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <limits>
|
#include <limits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
#include "device_controller.h"
|
#include "device_controller.h"
|
||||||
@@ -20,6 +21,47 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
|
void Render::OnSignalMessageCb(const char* message, size_t size,
|
||||||
|
void* user_data) {
|
||||||
|
Render* render = (Render*)user_data;
|
||||||
|
if (!render || !message || size == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string s(message, size);
|
||||||
|
auto j = nlohmann::json::parse(s, nullptr, false);
|
||||||
|
if (j.is_discarded() || !j.contains("type") || !j["type"].is_string()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
std::string type = j["type"].get<std::string>();
|
||||||
|
if (type == "presence") {
|
||||||
|
if (j.contains("devices") && j["devices"].is_array()) {
|
||||||
|
for (auto& dev : j["devices"]) {
|
||||||
|
if (!dev.is_object()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!dev.contains("id") || !dev["id"].is_string()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!dev.contains("online") || !dev["online"].is_boolean()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
std::string id = dev["id"].get<std::string>();
|
||||||
|
bool online = dev["online"].get<bool>();
|
||||||
|
render->device_presence_.SetOnline(id, online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (type == "presence_update") {
|
||||||
|
if (j.contains("id") && j["id"].is_string() && j.contains("online") &&
|
||||||
|
j["online"].is_boolean()) {
|
||||||
|
std::string id = j["id"].get<std::string>();
|
||||||
|
bool online = j["online"].get<bool>();
|
||||||
|
if (!id.empty()) {
|
||||||
|
render->device_presence_.SetOnline(id, online);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
int Render::SendKeyCommand(int key_code, bool is_down) {
|
int Render::SendKeyCommand(int key_code, bool is_down) {
|
||||||
RemoteAction remote_action;
|
RemoteAction remote_action;
|
||||||
remote_action.type = ControlType::keyboard;
|
remote_action.type = ControlType::keyboard;
|
||||||
@@ -30,17 +72,16 @@ int Render::SendKeyCommand(int key_code, bool is_down) {
|
|||||||
}
|
}
|
||||||
remote_action.k.key_value = key_code;
|
remote_action.k.key_value = key_code;
|
||||||
|
|
||||||
if (!controlled_remote_id_.empty()) {
|
std::string target_id = controlled_remote_id_.empty() ? focused_remote_id_
|
||||||
// std::shared_lock lock(client_properties_mutex_);
|
: controlled_remote_id_;
|
||||||
if (client_properties_.find(controlled_remote_id_) !=
|
if (!target_id.empty()) {
|
||||||
client_properties_.end()) {
|
if (client_properties_.find(target_id) != client_properties_.end()) {
|
||||||
auto props = client_properties_[controlled_remote_id_];
|
auto props = client_properties_[target_id];
|
||||||
if (props->connection_status_ == ConnectionStatus::Connected) {
|
if (props->connection_status_ == ConnectionStatus::Connected &&
|
||||||
|
props->peer_) {
|
||||||
std::string msg = remote_action.to_json();
|
std::string msg = remote_action.to_json();
|
||||||
if (props->peer_) {
|
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
|
||||||
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
|
props->data_label_.c_str());
|
||||||
props->data_label_.c_str());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -354,6 +395,13 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
|||||||
return;
|
return;
|
||||||
} else if (source_id == render->clipboard_label_) {
|
} else if (source_id == render->clipboard_label_) {
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
|
std::string remote_user_id(user_id, user_id_size);
|
||||||
|
auto props =
|
||||||
|
render->GetSubStreamWindowPropertiesByRemoteId(remote_user_id);
|
||||||
|
if (props && !props->enable_mouse_control_) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
std::string clipboard_text(data, size);
|
std::string clipboard_text(data, size);
|
||||||
if (!Clipboard::SetText(clipboard_text)) {
|
if (!Clipboard::SetText(clipboard_text)) {
|
||||||
LOG_ERROR("Failed to set clipboard content from remote");
|
LOG_ERROR("Failed to set clipboard content from remote");
|
||||||
@@ -450,9 +498,9 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
|||||||
const double bps =
|
const double bps =
|
||||||
(static_cast<double>(delta_bytes) * 8.0) / delta_seconds;
|
(static_cast<double>(delta_bytes) * 8.0) / delta_seconds;
|
||||||
if (bps > 0.0) {
|
if (bps > 0.0) {
|
||||||
const double capped =
|
const double capped = (std::min)(
|
||||||
(std::min)(bps, static_cast<double>(
|
bps,
|
||||||
(std::numeric_limits<uint32_t>::max)()));
|
static_cast<double>((std::numeric_limits<uint32_t>::max)()));
|
||||||
estimated_rate_bps = static_cast<uint32_t>(capped);
|
estimated_rate_bps = static_cast<uint32_t>(capped);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -568,13 +616,6 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
|||||||
remote_action.i.host_name, remote_action.i.host_name_size);
|
remote_action.i.host_name, remote_action.i.host_name_size);
|
||||||
LOG_INFO("Remote hostname: [{}]",
|
LOG_INFO("Remote hostname: [{}]",
|
||||||
render->connection_host_names_[remote_id]);
|
render->connection_host_names_[remote_id]);
|
||||||
|
|
||||||
for (int i = 0; i < remote_action.i.display_num; i++) {
|
|
||||||
render->display_info_list_.push_back(
|
|
||||||
DisplayInfo(remote_action.i.display_list[i],
|
|
||||||
remote_action.i.left[i], remote_action.i.top[i],
|
|
||||||
remote_action.i.right[i], remote_action.i.bottom[i]));
|
|
||||||
}
|
|
||||||
FreeRemoteAction(remote_action);
|
FreeRemoteAction(remote_action);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -614,6 +655,7 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
|
|||||||
render->signal_connected_ = false;
|
render->signal_connected_ = false;
|
||||||
} else if (SignalStatus::SignalConnected == status) {
|
} else if (SignalStatus::SignalConnected == status) {
|
||||||
render->signal_connected_ = true;
|
render->signal_connected_ = true;
|
||||||
|
render->need_to_send_recent_connections_ = true;
|
||||||
LOG_INFO("[{}] connected to signal server", client_id);
|
LOG_INFO("[{}] connected to signal server", client_id);
|
||||||
} else if (SignalStatus::SignalFailed == status) {
|
} else if (SignalStatus::SignalFailed == status) {
|
||||||
render->signal_connected_ = false;
|
render->signal_connected_ = false;
|
||||||
@@ -723,13 +765,14 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
|||||||
0, (int)render->title_bar_height_,
|
0, (int)render->title_bar_height_,
|
||||||
(int)render->stream_window_width_,
|
(int)render->stream_window_width_,
|
||||||
(int)(render->stream_window_height_ - render->title_bar_height_)};
|
(int)(render->stream_window_height_ - render->title_bar_height_)};
|
||||||
|
render->start_keyboard_capturer_ = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ConnectionStatus::Disconnected:
|
case ConnectionStatus::Disconnected:
|
||||||
case ConnectionStatus::Failed:
|
case ConnectionStatus::Failed:
|
||||||
case ConnectionStatus::Closed: {
|
case ConnectionStatus::Closed: {
|
||||||
props->connection_established_ = false;
|
props->connection_established_ = false;
|
||||||
props->mouse_control_button_pressed_ = false;
|
props->enable_mouse_control_ = false;
|
||||||
|
|
||||||
{
|
{
|
||||||
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
@@ -747,6 +790,8 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
|||||||
event.user.data1 = props.get();
|
event.user.data1 = props.get();
|
||||||
SDL_PushEvent(&event);
|
SDL_PushEvent(&event);
|
||||||
|
|
||||||
|
render->focus_on_stream_window_ = false;
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ConnectionStatus::IncorrectPassword: {
|
case ConnectionStatus::IncorrectPassword: {
|
||||||
@@ -826,12 +871,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
|||||||
render->start_screen_capturer_ = true;
|
render->start_screen_capturer_ = true;
|
||||||
render->start_speaker_capturer_ = true;
|
render->start_speaker_capturer_ = true;
|
||||||
render->remote_client_id_ = remote_id;
|
render->remote_client_id_ = remote_id;
|
||||||
#ifdef CROSSDESK_DEBUG
|
|
||||||
render->start_mouse_controller_ = false;
|
|
||||||
render->start_keyboard_capturer_ = false;
|
|
||||||
#else
|
|
||||||
render->start_mouse_controller_ = true;
|
render->start_mouse_controller_ = true;
|
||||||
#endif
|
|
||||||
if (std::all_of(render->connection_status_.begin(),
|
if (std::all_of(render->connection_status_.begin(),
|
||||||
render->connection_status_.end(), [](const auto& kv) {
|
render->connection_status_.end(), [](const auto& kv) {
|
||||||
return kv.first.find("web") != std::string::npos;
|
return kv.first.find("web") != std::string::npos;
|
||||||
@@ -841,6 +881,8 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
case ConnectionStatus::Disconnected:
|
||||||
|
case ConnectionStatus::Failed:
|
||||||
case ConnectionStatus::Closed: {
|
case ConnectionStatus::Closed: {
|
||||||
if (std::all_of(render->connection_status_.begin(),
|
if (std::all_of(render->connection_status_.begin(),
|
||||||
render->connection_status_.end(), [](const auto& kv) {
|
render->connection_status_.end(), [](const auto& kv) {
|
||||||
@@ -862,6 +904,10 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
|||||||
}
|
}
|
||||||
|
|
||||||
render->connection_status_.erase(remote_id);
|
render->connection_status_.erase(remote_id);
|
||||||
|
render->connection_host_names_.erase(remote_id);
|
||||||
|
if (render->screen_capturer_) {
|
||||||
|
render->screen_capturer_->ResetToInitialMonitor();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (std::all_of(render->connection_status_.begin(),
|
if (std::all_of(render->connection_status_.begin(),
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
float line_padding = title_bar_height_ * 0.12f;
|
float line_padding = title_bar_height_ * 0.12f;
|
||||||
float line_thickness = title_bar_height_ * 0.07f;
|
float line_thickness = title_bar_height_ * 0.07f;
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
if (props->control_bar_expand_) {
|
if (props->control_bar_expand_) {
|
||||||
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
ImGui::SetCursorPosX(props->is_control_bar_in_left_
|
||||||
? props->control_window_width_ * 0.03f
|
? props->control_window_width_ * 0.03f
|
||||||
@@ -198,24 +198,21 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
float mouse_y = ImGui::GetCursorScreenPos().y;
|
float mouse_y = ImGui::GetCursorScreenPos().y;
|
||||||
float disable_mouse_x = mouse_x + line_padding;
|
float disable_mouse_x = mouse_x + line_padding;
|
||||||
float disable_mouse_y = mouse_y + line_padding;
|
float disable_mouse_y = mouse_y + line_padding;
|
||||||
std::string mouse = props->mouse_control_button_pressed_
|
std::string mouse = ICON_FA_COMPUTER_MOUSE;
|
||||||
? ICON_FA_COMPUTER_MOUSE
|
|
||||||
: ICON_FA_COMPUTER_MOUSE;
|
|
||||||
ImGui::SetWindowFontScale(0.5f);
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
if (ImGui::Button(mouse.c_str(), ImVec2(button_width, button_height))) {
|
if (ImGui::Button(mouse.c_str(), ImVec2(button_width, button_height))) {
|
||||||
if (props->connection_established_) {
|
if (props->connection_established_) {
|
||||||
start_keyboard_capturer_ = !start_keyboard_capturer_;
|
start_keyboard_capturer_ = !start_keyboard_capturer_;
|
||||||
props->control_mouse_ = !props->control_mouse_;
|
props->control_mouse_ = !props->control_mouse_;
|
||||||
props->mouse_control_button_pressed_ =
|
props->enable_mouse_control_ = !props->enable_mouse_control_;
|
||||||
!props->mouse_control_button_pressed_;
|
|
||||||
props->mouse_control_button_label_ =
|
props->mouse_control_button_label_ =
|
||||||
props->mouse_control_button_pressed_
|
props->enable_mouse_control_
|
||||||
? localization::release_mouse[localization_language_index_]
|
? localization::release_mouse[localization_language_index_]
|
||||||
: localization::control_mouse[localization_language_index_];
|
: localization::control_mouse[localization_language_index_];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!props->mouse_control_button_pressed_) {
|
if (!props->enable_mouse_control_) {
|
||||||
draw_list->AddLine(ImVec2(disable_mouse_x, disable_mouse_y),
|
draw_list->AddLine(ImVec2(disable_mouse_x, disable_mouse_y),
|
||||||
ImVec2(mouse_x + button_width - line_padding,
|
ImVec2(mouse_x + button_width - line_padding,
|
||||||
mouse_y + button_height - line_padding),
|
mouse_y + button_height - line_padding),
|
||||||
|
|||||||
@@ -24,12 +24,12 @@ int Render::StatusBar() {
|
|||||||
ImVec2 dot_pos = ImVec2(status_bar_width * 0.025f,
|
ImVec2 dot_pos = ImVec2(status_bar_width * 0.025f,
|
||||||
io.DisplaySize.y * (1 - STATUS_BAR_HEIGHT * 0.5f));
|
io.DisplaySize.y * (1 - STATUS_BAR_HEIGHT * 0.5f));
|
||||||
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
ImDrawList* draw_list = ImGui::GetWindowDrawList();
|
||||||
|
draw_list->AddCircleFilled(dot_pos, status_bar_height * 0.25f,
|
||||||
|
ImColor(1.0f, 1.0f, 1.0f), 100);
|
||||||
draw_list->AddCircleFilled(dot_pos, status_bar_height * 0.2f,
|
draw_list->AddCircleFilled(dot_pos, status_bar_height * 0.2f,
|
||||||
ImColor(signal_connected_ ? 0.0f : 1.0f,
|
ImColor(signal_connected_ ? 0.0f : 1.0f,
|
||||||
signal_connected_ ? 1.0f : 0.0f, 0.0f),
|
signal_connected_ ? 1.0f : 0.0f, 0.0f),
|
||||||
100);
|
100);
|
||||||
draw_list->AddCircle(dot_pos, status_bar_height * 0.25f,
|
|
||||||
ImColor(1.0f, 1.0f, 1.0f), 100);
|
|
||||||
|
|
||||||
ImGui::SetWindowFontScale(0.6f);
|
ImGui::SetWindowFontScale(0.6f);
|
||||||
draw_list->AddText(
|
draw_list->AddText(
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ int Render::AboutWindow() {
|
|||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
ImGui::SetWindowFontScale(0.5f);
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
ImGui::Begin(
|
ImGui::Begin(
|
||||||
localization::about[localization_language_index_].c_str(), nullptr,
|
localization::about[localization_language_index_].c_str(), nullptr,
|
||||||
|
|||||||
@@ -15,10 +15,10 @@ bool Render::ConnectionStatusWindow(
|
|||||||
ImGui::SetNextWindowSize(
|
ImGui::SetNextWindowSize(
|
||||||
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
|
|
||||||
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
ImGui::Begin("ConnectionStatusWindow", nullptr,
|
||||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
|
||||||
@@ -36,6 +36,18 @@ bool Render::ConnectionStatusWindow(
|
|||||||
text = localization::p2p_connecting[localization_language_index_];
|
text = localization::p2p_connecting[localization_language_index_];
|
||||||
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
|
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
|
||||||
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
|
ImGui::SetCursorPosY(connection_status_window_height * 0.67f);
|
||||||
|
// cancel
|
||||||
|
if (ImGui::Button(
|
||||||
|
localization::cancel[localization_language_index_].c_str()) ||
|
||||||
|
ImGui::IsKeyPressed(ImGuiKey_Escape)) {
|
||||||
|
show_connection_status_window_ = false;
|
||||||
|
re_enter_remote_id_ = true;
|
||||||
|
LOG_INFO("User cancelled connecting to [{}]", props->remote_id_);
|
||||||
|
if (props->peer_) {
|
||||||
|
LeaveConnection(props->peer_, props->remote_id_.c_str());
|
||||||
|
}
|
||||||
|
ret_flag = true;
|
||||||
|
}
|
||||||
} else if (ConnectionStatus::Connected == props->connection_status_) {
|
} else if (ConnectionStatus::Connected == props->connection_status_) {
|
||||||
text = localization::p2p_connected[localization_language_index_];
|
text = localization::p2p_connected[localization_language_index_];
|
||||||
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
|
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
|
||||||
|
|||||||
@@ -43,9 +43,9 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_ * 1.5f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, window_rounding_ * 1.5f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
|
|
||||||
float y_boundary = fullscreen_button_pressed_ ? 0.0f : title_bar_height_;
|
float y_boundary = fullscreen_button_pressed_ ? 0.0f : title_bar_height_;
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ int Render::FileTransferWindow(
|
|||||||
ImGui::PushFont(stream_windows_system_chinese_font_);
|
ImGui::PushFont(stream_windows_system_chinese_font_);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_ * 0.5f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
|
||||||
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(0.0f, 0.0f, 0.0f, 0.3f));
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ int Render::SettingWindow() {
|
|||||||
int settings_items_offset = 0;
|
int settings_items_offset = 0;
|
||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
|
|
||||||
ImGui::Begin(localization::settings[localization_language_index_].c_str(),
|
ImGui::Begin(localization::settings[localization_language_index_].c_str(),
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
@@ -127,8 +127,8 @@ int Render::RequestPermissionWindow() {
|
|||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
|
||||||
|
|
||||||
ImGui::Begin(
|
ImGui::Begin(
|
||||||
|
|||||||
@@ -51,8 +51,8 @@ int Render::SelfHostedServerWindow() {
|
|||||||
{
|
{
|
||||||
ImGui::SetWindowFontScale(0.5f);
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
|
|
||||||
ImGui::Begin(localization::self_hosted_server_settings
|
ImGui::Begin(localization::self_hosted_server_settings
|
||||||
[localization_language_index_]
|
[localization_language_index_]
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ int Render::ServerWindow() {
|
|||||||
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
|
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
|
||||||
ImGuiCond_Always);
|
ImGuiCond_Always);
|
||||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::Begin("##server_window", nullptr,
|
ImGui::Begin("##server_window", nullptr,
|
||||||
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
|
||||||
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
|
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
|
||||||
@@ -61,7 +61,7 @@ int Render::ServerWindow() {
|
|||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
|
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
|
||||||
ImGui::BeginChild(
|
ImGui::BeginChild(
|
||||||
"ServerTitleBar",
|
"ServerTitleBar",
|
||||||
@@ -148,33 +148,38 @@ int Render::RemoteClientInfoWindow() {
|
|||||||
|
|
||||||
float font_scale = localization_language_index_ == 0 ? 0.5f : 0.45f;
|
float font_scale = localization_language_index_ == 0 ? 0.5f : 0.45f;
|
||||||
|
|
||||||
std::vector<std::string> remote_hostnames;
|
std::vector<std::pair<std::string, std::string>> remote_entries;
|
||||||
remote_hostnames.reserve(connection_host_names_.size());
|
remote_entries.reserve(connection_status_.size());
|
||||||
for (const auto& kv : connection_host_names_) {
|
for (const auto& kv : connection_status_) {
|
||||||
remote_hostnames.push_back(kv.second);
|
const auto host_it = connection_host_names_.find(kv.first);
|
||||||
|
const std::string display_name =
|
||||||
|
(host_it != connection_host_names_.end() && !host_it->second.empty())
|
||||||
|
? host_it->second
|
||||||
|
: kv.first;
|
||||||
|
remote_entries.emplace_back(kv.first, display_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto find_remote_id_by_hostname =
|
auto find_display_name_by_remote_id =
|
||||||
[this](const std::string& hostname) -> std::string {
|
[&remote_entries](const std::string& remote_id) -> std::string {
|
||||||
for (const auto& kv : connection_host_names_) {
|
for (const auto& entry : remote_entries) {
|
||||||
if (kv.second == hostname) {
|
if (entry.first == remote_id) {
|
||||||
return kv.first;
|
return entry.second;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!selected_server_remote_hostname_.empty()) {
|
if (!selected_server_remote_id_.empty() &&
|
||||||
if (std::find(remote_hostnames.begin(), remote_hostnames.end(),
|
find_display_name_by_remote_id(selected_server_remote_id_).empty()) {
|
||||||
selected_server_remote_hostname_) == remote_hostnames.end()) {
|
selected_server_remote_id_.clear();
|
||||||
selected_server_remote_hostname_.clear();
|
selected_server_remote_hostname_.clear();
|
||||||
selected_server_remote_id_.clear();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (selected_server_remote_hostname_.empty() && !remote_hostnames.empty()) {
|
if (selected_server_remote_id_.empty() && !remote_entries.empty()) {
|
||||||
selected_server_remote_hostname_ = remote_hostnames.front();
|
selected_server_remote_id_ = remote_entries.front().first;
|
||||||
selected_server_remote_id_ =
|
}
|
||||||
find_remote_id_by_hostname(selected_server_remote_hostname_);
|
if (!selected_server_remote_id_.empty()) {
|
||||||
|
selected_server_remote_hostname_ =
|
||||||
|
find_display_name_by_remote_id(selected_server_remote_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
ImGui::SetWindowFontScale(font_scale);
|
ImGui::SetWindowFontScale(font_scale);
|
||||||
@@ -196,13 +201,12 @@ int Render::RemoteClientInfoWindow() {
|
|||||||
ImGui::AlignTextToFramePadding();
|
ImGui::AlignTextToFramePadding();
|
||||||
if (ImGui::BeginCombo("##server_remote_id", selected_preview)) {
|
if (ImGui::BeginCombo("##server_remote_id", selected_preview)) {
|
||||||
ImGui::SetWindowFontScale(localization_language_index_ == 0 ? 0.45f : 0.4f);
|
ImGui::SetWindowFontScale(localization_language_index_ == 0 ? 0.45f : 0.4f);
|
||||||
for (int i = 0; i < static_cast<int>(remote_hostnames.size()); i++) {
|
for (int i = 0; i < static_cast<int>(remote_entries.size()); i++) {
|
||||||
const bool selected =
|
const bool selected =
|
||||||
(remote_hostnames[i] == selected_server_remote_hostname_);
|
(remote_entries[i].first == selected_server_remote_id_);
|
||||||
if (ImGui::Selectable(remote_hostnames[i].c_str(), selected)) {
|
if (ImGui::Selectable(remote_entries[i].second.c_str(), selected)) {
|
||||||
selected_server_remote_hostname_ = remote_hostnames[i];
|
selected_server_remote_id_ = remote_entries[i].first;
|
||||||
selected_server_remote_id_ =
|
selected_server_remote_hostname_ = remote_entries[i].second;
|
||||||
find_remote_id_by_hostname(selected_server_remote_hostname_);
|
|
||||||
}
|
}
|
||||||
if (selected) {
|
if (selected) {
|
||||||
ImGui::SetItemDefaultFocus();
|
ImGui::SetItemDefaultFocus();
|
||||||
@@ -358,7 +362,7 @@ int Render::RemoteClientInfoWindow() {
|
|||||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1.0f, 0.0f, 0.0f, 1.0f));
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0.3f, 0.3f, 1.0f));
|
||||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0.5f, 0.5f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0.5f, 0.5f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 5.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_);
|
||||||
ImGui::SetWindowFontScale(font_scale);
|
ImGui::SetWindowFontScale(font_scale);
|
||||||
if (ImGui::Button(ICON_FA_XMARK, ImVec2(close_connection_button_width,
|
if (ImGui::Button(ICON_FA_XMARK, ImVec2(close_connection_button_width,
|
||||||
close_connection_button_height))) {
|
close_connection_button_height))) {
|
||||||
|
|||||||
@@ -31,6 +31,34 @@ void Render::DrawConnectionStatusText(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void Render::DrawReceivingScreenText(
|
||||||
|
std::shared_ptr<SubStreamWindowProperties>& props) {
|
||||||
|
if (!props->connection_established_ ||
|
||||||
|
props->connection_status_ != ConnectionStatus::Connected) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_valid_frame = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
has_valid_frame = props->stream_texture_ != nullptr &&
|
||||||
|
props->video_width_ > 0 && props->video_height_ > 0 &&
|
||||||
|
props->front_frame_ && !props->front_frame_->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_valid_frame) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string& text =
|
||||||
|
localization::receiving_screen[localization_language_index_];
|
||||||
|
ImVec2 size = ImGui::GetWindowSize();
|
||||||
|
ImVec2 text_size = ImGui::CalcTextSize(text.c_str());
|
||||||
|
ImGui::SetCursorPos(
|
||||||
|
ImVec2((size.x - text_size.x) * 0.5f, (size.y - text_size.y) * 0.5f));
|
||||||
|
ImGui::TextColored(ImVec4(1.0f, 1.0f, 1.0f, 0.92f), "%s", text.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
void Render::CloseTab(decltype(client_properties_)::iterator& it) {
|
void Render::CloseTab(decltype(client_properties_)::iterator& it) {
|
||||||
// std::unique_lock lock(client_properties_mutex_);
|
// std::unique_lock lock(client_properties_mutex_);
|
||||||
if (it != client_properties_.end()) {
|
if (it != client_properties_.end()) {
|
||||||
@@ -144,6 +172,8 @@ int Render::StreamWindow() {
|
|||||||
// Show file transfer window if needed
|
// Show file transfer window if needed
|
||||||
FileTransferWindow(props);
|
FileTransferWindow(props);
|
||||||
|
|
||||||
|
DrawReceivingScreenText(props);
|
||||||
|
|
||||||
focused_remote_id_ = props->remote_id_;
|
focused_remote_id_ = props->remote_id_;
|
||||||
|
|
||||||
if (!props->peer_) {
|
if (!props->peer_) {
|
||||||
@@ -244,6 +274,8 @@ int Render::StreamWindow() {
|
|||||||
// Show file transfer window if needed
|
// Show file transfer window if needed
|
||||||
FileTransferWindow(props);
|
FileTransferWindow(props);
|
||||||
|
|
||||||
|
DrawReceivingScreenText(props);
|
||||||
|
|
||||||
ImGui::End();
|
ImGui::End();
|
||||||
|
|
||||||
if (!props->peer_) {
|
if (!props->peer_) {
|
||||||
|
|||||||
@@ -77,8 +77,8 @@ int Render::UpdateNotificationWindow() {
|
|||||||
|
|
||||||
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 6.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
|
||||||
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
|
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
|
||||||
ImGui::Begin(
|
ImGui::Begin(
|
||||||
localization::notification[localization_language_index_].c_str(),
|
localization::notification[localization_language_index_].c_str(),
|
||||||
nullptr,
|
nullptr,
|
||||||
|
|||||||
@@ -138,6 +138,10 @@ int ScreenCapturerX11::SwitchTo(int monitor_index) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerX11::ResetToInitialMonitor() {
|
||||||
|
monitor_index_ = initial_monitor_index_;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
|
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
|
||||||
return display_info_list_;
|
return display_info_list_;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
|
|||||||
int Resume(int monitor_index) override;
|
int Resume(int monitor_index) override;
|
||||||
|
|
||||||
int SwitchTo(int monitor_index) override;
|
int SwitchTo(int monitor_index) override;
|
||||||
|
int ResetToInitialMonitor() override;
|
||||||
|
|
||||||
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||||
|
|
||||||
@@ -62,6 +63,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
|
|||||||
std::atomic<bool> running_{false};
|
std::atomic<bool> running_{false};
|
||||||
std::atomic<bool> paused_{false};
|
std::atomic<bool> paused_{false};
|
||||||
std::atomic<int> monitor_index_{0};
|
std::atomic<int> monitor_index_{0};
|
||||||
|
int initial_monitor_index_ = 0;
|
||||||
std::atomic<bool> show_cursor_{true};
|
std::atomic<bool> show_cursor_{true};
|
||||||
int fps_ = 60;
|
int fps_ = 60;
|
||||||
cb_desktop_data callback_;
|
cb_desktop_data callback_;
|
||||||
|
|||||||
@@ -62,6 +62,13 @@ int ScreenCapturerSck::SwitchTo(int monitor_index) {
|
|||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerSck::ResetToInitialMonitor() {
|
||||||
|
if (screen_capturer_sck_impl_) {
|
||||||
|
return screen_capturer_sck_impl_->ResetToInitialMonitor();
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
|
std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
|
||||||
if (screen_capturer_sck_impl_) {
|
if (screen_capturer_sck_impl_) {
|
||||||
return screen_capturer_sck_impl_->GetDisplayInfoList();
|
return screen_capturer_sck_impl_->GetDisplayInfoList();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ class ScreenCapturerSck : public ScreenCapturer {
|
|||||||
int Resume(int monitor_index) override;
|
int Resume(int monitor_index) override;
|
||||||
|
|
||||||
int SwitchTo(int monitor_index) override;
|
int SwitchTo(int monitor_index) override;
|
||||||
|
int ResetToInitialMonitor() override;
|
||||||
|
|
||||||
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||||
|
|
||||||
|
|||||||
@@ -70,6 +70,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
int Resume(int monitor_index) override { return 0; }
|
int Resume(int monitor_index) override { return 0; }
|
||||||
|
|
||||||
std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; }
|
std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; }
|
||||||
|
int ResetToInitialMonitor() override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::vector<DisplayInfo> display_info_list_;
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
@@ -113,6 +114,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
|
|||||||
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
|
// Currently selected display, or 0 if the full desktop is selected. This capturer does not
|
||||||
// support full-desktop capture, and will fall back to the first display.
|
// support full-desktop capture, and will fall back to the first display.
|
||||||
CGDirectDisplayID current_display_ = 0;
|
CGDirectDisplayID current_display_ = 0;
|
||||||
|
int initial_monitor_index_ = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
std::string GetDisplayName(CGDirectDisplayID display_id) {
|
std::string GetDisplayName(CGDirectDisplayID display_id) {
|
||||||
@@ -261,6 +263,7 @@ int ScreenCapturerSckImpl::Init(const int fps, cb_desktop_data cb) {
|
|||||||
display_id_name_map_[display_id] = name;
|
display_id_name_map_[display_id] = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
initial_monitor_index_ = 0;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +298,25 @@ int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerSckImpl::ResetToInitialMonitor() {
|
||||||
|
int target = initial_monitor_index_;
|
||||||
|
if (display_info_list_.empty()) return -1;
|
||||||
|
CGDirectDisplayID target_display = display_id_map_[target];
|
||||||
|
if (current_display_ == target_display) return 0;
|
||||||
|
if (stream_) {
|
||||||
|
[stream_ stopCaptureWithCompletionHandler:^(NSError *error) {
|
||||||
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
|
stream_ = nil;
|
||||||
|
current_display_ = target_display;
|
||||||
|
StartOrReconfigureCapturer();
|
||||||
|
}];
|
||||||
|
} else {
|
||||||
|
current_display_ = target_display;
|
||||||
|
StartOrReconfigureCapturer();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
int ScreenCapturerSckImpl::Destroy() {
|
int ScreenCapturerSckImpl::Destroy() {
|
||||||
std::lock_guard<std::mutex> lock(lock_);
|
std::lock_guard<std::mutex> lock(lock_);
|
||||||
if (stream_) {
|
if (stream_) {
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ class ScreenCapturer {
|
|||||||
|
|
||||||
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
|
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
|
||||||
virtual int SwitchTo(int monitor_index) = 0;
|
virtual int SwitchTo(int monitor_index) = 0;
|
||||||
|
virtual int ResetToInitialMonitor() = 0;
|
||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
@@ -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__
|
||||||
@@ -37,4 +37,4 @@ class ScreenCapturerFactory {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
356
src/screen_capturer/windows/screen_capturer_dxgi.cpp
Normal file
356
src/screen_capturer/windows/screen_capturer_dxgi.cpp
Normal file
@@ -0,0 +1,356 @@
|
|||||||
|
#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;
|
||||||
|
initial_monitor_index_ = monitor_index_;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerDxgi::ResetToInitialMonitor() {
|
||||||
|
if (display_info_list_.empty()) return -1;
|
||||||
|
int target = initial_monitor_index_;
|
||||||
|
if (target < 0 || target >= (int)display_info_list_.size()) return -1;
|
||||||
|
if (monitor_index_ == target) return 0;
|
||||||
|
if (running_) {
|
||||||
|
paused_ = true;
|
||||||
|
monitor_index_ = target;
|
||||||
|
ReleaseDuplication();
|
||||||
|
if (!CreateDuplicationForMonitor(monitor_index_)) {
|
||||||
|
paused_ = false;
|
||||||
|
return -2;
|
||||||
|
}
|
||||||
|
paused_ = false;
|
||||||
|
LOG_INFO("DXGI: reset to initial monitor {}:{}", monitor_index_.load(),
|
||||||
|
display_info_list_[monitor_index_].name);
|
||||||
|
} else {
|
||||||
|
monitor_index_ = target;
|
||||||
|
}
|
||||||
|
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
|
||||||
81
src/screen_capturer/windows/screen_capturer_dxgi.h
Normal file
81
src/screen_capturer/windows/screen_capturer_dxgi.h
Normal file
@@ -0,0 +1,81 @@
|
|||||||
|
/*
|
||||||
|
* @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;
|
||||||
|
int ResetToInitialMonitor() 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};
|
||||||
|
int initial_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
|
||||||
217
src/screen_capturer/windows/screen_capturer_gdi.cpp
Normal file
217
src/screen_capturer/windows/screen_capturer_gdi.cpp
Normal file
@@ -0,0 +1,217 @@
|
|||||||
|
#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;
|
||||||
|
initial_monitor_index_ = monitor_index_;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerGdi::ResetToInitialMonitor() {
|
||||||
|
if (display_info_list_.empty()) return -1;
|
||||||
|
int target = initial_monitor_index_;
|
||||||
|
if (target < 0 || target >= (int)display_info_list_.size()) return -1;
|
||||||
|
monitor_index_ = target;
|
||||||
|
LOG_INFO("GDI: reset to initial 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
|
||||||
68
src/screen_capturer/windows/screen_capturer_gdi.h
Normal file
68
src/screen_capturer/windows/screen_capturer_gdi.h
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
/*
|
||||||
|
* @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;
|
||||||
|
int ResetToInitialMonitor() 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};
|
||||||
|
int initial_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
|
||||||
@@ -56,8 +56,6 @@ BOOL WINAPI EnumMonitorProc(HMONITOR hmonitor, [[maybe_unused]] HDC hdc,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (monitor_info_.dwFlags == DISPLAY_DEVICE_MIRRORING_DRIVER) return true;
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,6 +147,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
|
|||||||
LOG_INFO("Default on monitor {}:{}", monitor_index_,
|
LOG_INFO("Default on monitor {}:{}", monitor_index_,
|
||||||
display_info_list_[monitor_index_].name);
|
display_info_list_[monitor_index_].name);
|
||||||
|
|
||||||
|
initial_monitor_index_ = monitor_index_;
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -165,6 +164,8 @@ int ScreenCapturerWgc::Start(bool show_cursor) {
|
|||||||
return 4;
|
return 4;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool any_started = false;
|
||||||
|
int last_error = 0;
|
||||||
for (int i = 0; i < sessions_.size(); i++) {
|
for (int i = 0; i < sessions_.size(); i++) {
|
||||||
if (sessions_[i].inited_ == false) {
|
if (sessions_[i].inited_ == false) {
|
||||||
LOG_ERROR("Session {} not inited", i);
|
LOG_ERROR("Session {} not inited", i);
|
||||||
@@ -174,17 +175,27 @@ int ScreenCapturerWgc::Start(bool show_cursor) {
|
|||||||
if (sessions_[i].running_) {
|
if (sessions_[i].running_) {
|
||||||
LOG_ERROR("Session {} is already running", i);
|
LOG_ERROR("Session {} is already running", i);
|
||||||
} else {
|
} else {
|
||||||
sessions_[i].session_->Start(show_cursor);
|
int ret = sessions_[i].session_->Start(show_cursor);
|
||||||
|
if (ret != 0) {
|
||||||
|
LOG_ERROR("Session {} start failed, ret={}", i, ret);
|
||||||
|
last_error = ret;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
if (i != 0) {
|
if (i != 0) {
|
||||||
sessions_[i].session_->Pause();
|
sessions_[i].session_->Pause();
|
||||||
sessions_[i].paused_ = true;
|
sessions_[i].paused_ = true;
|
||||||
}
|
}
|
||||||
sessions_[i].running_ = true;
|
sessions_[i].running_ = true;
|
||||||
|
any_started = true;
|
||||||
}
|
}
|
||||||
running_ = true;
|
running_ = running_ || any_started;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!any_started) {
|
||||||
|
LOG_ERROR("WGC: no session started successfully");
|
||||||
|
return last_error != 0 ? last_error : -1;
|
||||||
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +268,26 @@ int ScreenCapturerWgc::SwitchTo(int monitor_index) {
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWgc::ResetToInitialMonitor() {
|
||||||
|
if (display_info_list_.empty()) return -1;
|
||||||
|
if (initial_monitor_index_ < 0 ||
|
||||||
|
initial_monitor_index_ >= static_cast<int>(display_info_list_.size())) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if (monitor_index_ == initial_monitor_index_) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (running_) {
|
||||||
|
Pause(monitor_index_);
|
||||||
|
}
|
||||||
|
monitor_index_ = initial_monitor_index_;
|
||||||
|
LOG_INFO("Reset to initial monitor {}:{}", monitor_index_,
|
||||||
|
display_info_list_[monitor_index_].name);
|
||||||
|
if (running_) {
|
||||||
|
Resume(monitor_index_);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
void ScreenCapturerWgc::OnFrame(const WgcSession::wgc_session_frame& frame,
|
||||||
int id) {
|
int id) {
|
||||||
if (!running_ || !on_data_) {
|
if (!running_ || !on_data_) {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
|||||||
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
|
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
|
||||||
|
|
||||||
int SwitchTo(int monitor_index);
|
int SwitchTo(int monitor_index);
|
||||||
|
int ResetToInitialMonitor() override;
|
||||||
|
|
||||||
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
|
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
|
||||||
|
|
||||||
@@ -45,6 +46,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
|||||||
MONITORINFOEX monitor_info_;
|
MONITORINFOEX monitor_info_;
|
||||||
std::vector<DisplayInfo> display_info_list_;
|
std::vector<DisplayInfo> display_info_list_;
|
||||||
int monitor_index_ = 0;
|
int monitor_index_ = 0;
|
||||||
|
int initial_monitor_index_ = 0;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
class WgcSessionInfo {
|
class WgcSessionInfo {
|
||||||
@@ -57,8 +59,8 @@ class ScreenCapturerWgc : public ScreenCapturer,
|
|||||||
|
|
||||||
std::vector<WgcSessionInfo> sessions_;
|
std::vector<WgcSessionInfo> sessions_;
|
||||||
|
|
||||||
std::atomic_bool running_;
|
std::atomic_bool running_{false};
|
||||||
std::atomic_bool inited_;
|
std::atomic_bool inited_{false};
|
||||||
|
|
||||||
int fps_ = 60;
|
int fps_ = 60;
|
||||||
|
|
||||||
|
|||||||
203
src/screen_capturer/windows/screen_capturer_win.cpp
Normal file
203
src/screen_capturer/windows/screen_capturer_win.cpp
Normal file
@@ -0,0 +1,203 @@
|
|||||||
|
#include "screen_capturer_win.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include <memory>
|
||||||
|
#include <string>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#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_orig_ = cb;
|
||||||
|
cb_ = [this](unsigned char* data, int size, int w, int h,
|
||||||
|
const char* display_name) {
|
||||||
|
std::string mapped_name;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
|
auto it = label_alias_.find(display_name);
|
||||||
|
if (it != label_alias_.end())
|
||||||
|
mapped_name = it->second;
|
||||||
|
else
|
||||||
|
mapped_name = display_name;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
|
if (canonical_labels_.find(mapped_name) == canonical_labels_.end()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (cb_orig_) cb_orig_(data, size, w, h, mapped_name.c_str());
|
||||||
|
};
|
||||||
|
|
||||||
|
int ret = -1;
|
||||||
|
|
||||||
|
impl_ = std::make_unique<ScreenCapturerWgc>();
|
||||||
|
ret = impl_->Init(fps_, cb_);
|
||||||
|
if (ret == 0) {
|
||||||
|
LOG_INFO("Windows capturer: using WGC");
|
||||||
|
BuildCanonicalFromImpl();
|
||||||
|
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");
|
||||||
|
BuildCanonicalFromImpl();
|
||||||
|
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");
|
||||||
|
BuildCanonicalFromImpl();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR("Windows capturer: all implementations failed, ret={}", ret);
|
||||||
|
impl_.reset();
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Destroy() {
|
||||||
|
if (impl_) {
|
||||||
|
impl_->Destroy();
|
||||||
|
impl_.reset();
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
|
label_alias_.clear();
|
||||||
|
handle_to_canonical_.clear();
|
||||||
|
canonical_labels_.clear();
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::Start(bool show_cursor) {
|
||||||
|
if (!impl_) return -1;
|
||||||
|
int ret = impl_->Start(show_cursor);
|
||||||
|
if (ret == 0) return 0;
|
||||||
|
|
||||||
|
LOG_WARN("Windows capturer: Start failed (ret={}), trying fallback", ret);
|
||||||
|
|
||||||
|
auto try_init_start = [&](std::unique_ptr<ScreenCapturer> cand) -> bool {
|
||||||
|
int r = cand->Init(fps_, cb_);
|
||||||
|
if (r != 0) return false;
|
||||||
|
int s = cand->Start(show_cursor);
|
||||||
|
if (s == 0) {
|
||||||
|
impl_ = std::move(cand);
|
||||||
|
RebuildAliasesFromImpl();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
|
||||||
|
if (dynamic_cast<ScreenCapturerWgc*>(impl_.get())) {
|
||||||
|
if (try_init_start(std::make_unique<ScreenCapturerDxgi>())) {
|
||||||
|
LOG_INFO("Windows capturer: fallback to DXGI");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
if (try_init_start(std::make_unique<ScreenCapturerGdi>())) {
|
||||||
|
LOG_INFO("Windows capturer: fallback to GDI");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
} else if (dynamic_cast<ScreenCapturerDxgi*>(impl_.get())) {
|
||||||
|
if (try_init_start(std::make_unique<ScreenCapturerGdi>())) {
|
||||||
|
LOG_INFO("Windows capturer: fallback to GDI");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG_ERROR("Windows capturer: all fallbacks failed to start");
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
int ScreenCapturerWin::ResetToInitialMonitor() {
|
||||||
|
if (!impl_) return -1;
|
||||||
|
return impl_->ResetToInitialMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<DisplayInfo> ScreenCapturerWin::GetDisplayInfoList() {
|
||||||
|
if (!impl_) return {};
|
||||||
|
return impl_->GetDisplayInfoList();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerWin::BuildCanonicalFromImpl() {
|
||||||
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
|
handle_to_canonical_.clear();
|
||||||
|
label_alias_.clear();
|
||||||
|
canonical_displays_ = impl_->GetDisplayInfoList();
|
||||||
|
canonical_labels_.clear();
|
||||||
|
for (const auto& di : canonical_displays_) {
|
||||||
|
handle_to_canonical_[di.handle] = di.name;
|
||||||
|
canonical_labels_.insert(di.name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void ScreenCapturerWin::RebuildAliasesFromImpl() {
|
||||||
|
std::lock_guard<std::mutex> lock(alias_mutex_);
|
||||||
|
label_alias_.clear();
|
||||||
|
auto current = impl_->GetDisplayInfoList();
|
||||||
|
auto similar = [&](const DisplayInfo& a, const DisplayInfo& b) {
|
||||||
|
int dl = std::abs(a.left - b.left);
|
||||||
|
int dt = std::abs(a.top - b.top);
|
||||||
|
int dw = std::abs(a.width - b.width);
|
||||||
|
int dh = std::abs(a.height - b.height);
|
||||||
|
return dl <= 10 && dt <= 10 && dw <= 20 && dh <= 20;
|
||||||
|
};
|
||||||
|
for (const auto& di : current) {
|
||||||
|
std::string canonical;
|
||||||
|
auto it = handle_to_canonical_.find(di.handle);
|
||||||
|
if (it != handle_to_canonical_.end()) {
|
||||||
|
canonical = it->second;
|
||||||
|
} else {
|
||||||
|
for (const auto& c : canonical_displays_) {
|
||||||
|
if (similar(di, c) || (di.is_primary && c.is_primary)) {
|
||||||
|
canonical = c.name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!canonical.empty() && canonical != di.name) {
|
||||||
|
label_alias_[di.name] = canonical;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace crossdesk
|
||||||
55
src/screen_capturer/windows/screen_capturer_win.h
Normal file
55
src/screen_capturer/windows/screen_capturer_win.h
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* @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 <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <unordered_set>
|
||||||
|
#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;
|
||||||
|
int ResetToInitialMonitor() override;
|
||||||
|
|
||||||
|
std::vector<DisplayInfo> GetDisplayInfoList() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<ScreenCapturer> impl_;
|
||||||
|
int fps_ = 60;
|
||||||
|
cb_desktop_data cb_;
|
||||||
|
cb_desktop_data cb_orig_;
|
||||||
|
|
||||||
|
std::unordered_map<void*, std::string> handle_to_canonical_;
|
||||||
|
std::unordered_map<std::string, std::string> label_alias_;
|
||||||
|
std::mutex alias_mutex_;
|
||||||
|
std::vector<DisplayInfo> canonical_displays_;
|
||||||
|
std::unordered_set<std::string> canonical_labels_;
|
||||||
|
|
||||||
|
void BuildCanonicalFromImpl();
|
||||||
|
void RebuildAliasesFromImpl();
|
||||||
|
};
|
||||||
|
} // namespace crossdesk
|
||||||
|
#endif
|
||||||
@@ -7,6 +7,8 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include "rd_log.h"
|
||||||
|
|
||||||
#define CHECK_INIT \
|
#define CHECK_INIT \
|
||||||
if (!is_initialized_) { \
|
if (!is_initialized_) { \
|
||||||
std::cout << "AE_NEED_INIT" << std::endl; \
|
std::cout << "AE_NEED_INIT" << std::endl; \
|
||||||
@@ -64,6 +66,7 @@ int WgcSessionImpl::Start(bool show_cursor) {
|
|||||||
|
|
||||||
CHECK_INIT;
|
CHECK_INIT;
|
||||||
try {
|
try {
|
||||||
|
last_show_cursor_ = show_cursor;
|
||||||
if (!capture_session_) {
|
if (!capture_session_) {
|
||||||
auto current_size = capture_item_.Size();
|
auto current_size = capture_item_.Size();
|
||||||
capture_framepool_ =
|
capture_framepool_ =
|
||||||
@@ -89,13 +92,12 @@ int WgcSessionImpl::Start(bool show_cursor) {
|
|||||||
// we need to test the performance later
|
// we need to test the performance later
|
||||||
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
|
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
|
||||||
|
|
||||||
capture_session_.StartCapture();
|
|
||||||
|
|
||||||
capture_session_.IsCursorCaptureEnabled(show_cursor);
|
capture_session_.IsCursorCaptureEnabled(show_cursor);
|
||||||
|
capture_session_.StartCapture();
|
||||||
|
|
||||||
error = 0;
|
error = 0;
|
||||||
} catch (winrt::hresult_error) {
|
} catch (winrt::hresult_error) {
|
||||||
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
|
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
||||||
return 86;
|
return 86;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return 86;
|
return 86;
|
||||||
@@ -246,8 +248,15 @@ void WgcSessionImpl::OnFrame(
|
|||||||
auto frame_captured =
|
auto frame_captured =
|
||||||
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
|
||||||
|
|
||||||
if (!d3d11_texture_mapped_ || is_new_size)
|
if (!d3d11_texture_mapped_ || is_new_size) {
|
||||||
CreateMappedTexture(frame_captured);
|
HRESULT tex_hr = CreateMappedTexture(frame_captured);
|
||||||
|
if (FAILED(tex_hr)) {
|
||||||
|
OutputDebugStringW(
|
||||||
|
(L"CreateMappedTexture failed: " + std::to_wstring(tex_hr))
|
||||||
|
.c_str());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
|
d3d11_device_context_->CopyResource(d3d11_texture_mapped_.get(),
|
||||||
frame_captured.get());
|
frame_captured.get());
|
||||||
@@ -262,6 +271,7 @@ void WgcSessionImpl::OnFrame(
|
|||||||
if (FAILED(hr)) {
|
if (FAILED(hr)) {
|
||||||
OutputDebugStringW(
|
OutputDebugStringW(
|
||||||
(L"map resource failed: " + std::to_wstring(hr)).c_str());
|
(L"map resource failed: " + std::to_wstring(hr)).c_str());
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// copy data from map_result.pData
|
// copy data from map_result.pData
|
||||||
@@ -290,7 +300,24 @@ void WgcSessionImpl::OnFrame(
|
|||||||
void WgcSessionImpl::OnClosed(
|
void WgcSessionImpl::OnClosed(
|
||||||
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
|
winrt::Windows::Graphics::Capture::GraphicsCaptureItem const&,
|
||||||
winrt::Windows::Foundation::IInspectable const&) {
|
winrt::Windows::Foundation::IInspectable const&) {
|
||||||
OutputDebugStringW(L"WgcSessionImpl::OnClosed");
|
std::lock_guard locker(lock_);
|
||||||
|
try {
|
||||||
|
CleanUp();
|
||||||
|
is_initialized_ = false;
|
||||||
|
if (Initialize() == 0) {
|
||||||
|
int ret = Start(last_show_cursor_);
|
||||||
|
if (ret == 0) {
|
||||||
|
OutputDebugStringW(L"WgcSessionImpl::OnClosed: auto recovered");
|
||||||
|
} else {
|
||||||
|
OutputDebugStringW(L"WgcSessionImpl::OnClosed: recover Start failed");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
OutputDebugStringW(
|
||||||
|
L"WgcSessionImpl::OnClosed: recover Initialize failed");
|
||||||
|
}
|
||||||
|
} catch (...) {
|
||||||
|
OutputDebugStringW(L"WgcSessionImpl::OnClosed: exception during recover");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int WgcSessionImpl::Initialize() {
|
int WgcSessionImpl::Initialize() {
|
||||||
@@ -313,7 +340,7 @@ int WgcSessionImpl::Initialize() {
|
|||||||
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
|
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
|
||||||
|
|
||||||
} catch (winrt::hresult_error) {
|
} catch (winrt::hresult_error) {
|
||||||
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
|
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
|
||||||
return 86;
|
return 86;
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return 86;
|
return 86;
|
||||||
@@ -378,4 +405,4 @@ LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
|
|||||||
// ::CloseWindow(hwnd_);
|
// ::CloseWindow(hwnd_);
|
||||||
// ::DestroyWindow(hwnd_);
|
// ::DestroyWindow(hwnd_);
|
||||||
// }
|
// }
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ class WgcSessionImpl : public WgcSession {
|
|||||||
bool is_initialized_ = false;
|
bool is_initialized_ = false;
|
||||||
bool is_running_ = false;
|
bool is_running_ = false;
|
||||||
bool is_paused_ = false;
|
bool is_paused_ = false;
|
||||||
|
bool last_show_cursor_ = false;
|
||||||
|
|
||||||
wgc_session_observer* observer_ = nullptr;
|
wgc_session_observer* observer_ = nullptr;
|
||||||
|
|
||||||
@@ -116,4 +117,4 @@ class WgcSessionImpl : public WgcSession {
|
|||||||
// return result;
|
// return result;
|
||||||
// }
|
// }
|
||||||
} // namespace crossdesk
|
} // namespace crossdesk
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
Submodule submodules/minirtc updated: dda294d178...a0001b1faf
@@ -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