Compare commits

..

42 Commits

Author SHA1 Message Date
dijunkun
b230b851e4 [fix] fix cannot close connection from Server Window when the peer is a web client 2026-03-10 00:39:00 +08:00
dijunkun
ff32477ffe [fix] update MiniRTC: fix crash on disconnect 2026-03-10 00:35:33 +08:00
dijunkun
c6c60decdb [fix] fix incorrect online status of recently connections 2026-03-09 22:52:05 +08:00
dijunkun
7505adeca8 [feat] update MiniRTC 2026-03-09 22:50:42 +08:00
dijunkun
754f1fba88 [feat] show 'Receiving screen' text before the remote frame arrives 2026-03-09 22:37:50 +08:00
dijunkun
8be46b870a [feat] add cancel button during connecting 2026-03-09 21:35:21 +08:00
dijunkun
81cb1d6c0b [fix] disable clipboard sharing when not in control mode 2026-03-05 17:46:27 +08:00
dijunkun
319416f1b7 [feat] update MiniRTC: optimize video quality and smoothness 2026-03-05 17:30:05 +08:00
dijunkun
d679c6251b [feat] update MiniRTC 2026-03-04 10:46:21 +08:00
dijunkun
a14baafda7 [fix] fix keyboard event loss due to start_keyboard_capturer_ flag improper setting, fixes #65 2026-03-04 10:36:39 +08:00
dijunkun
cfdc7d3106 [fix] update MiniRTC: fix bandwidth degradation caused by ALR-triggered resolution downgrade during static frames 2026-03-03 10:58:38 +08:00
dijunkun
33d51b8ce5 [fix] reset to initial monitor on connection close via ResetToInitialMonitor to fix black screen 2026-03-02 15:42:44 +08:00
dijunkun
b13dac2093 [feat] refine display of recent connections presence tooltip 2026-03-02 10:48:16 +08:00
dijunkun
a605c95e5a [fix] fix window rounding inconsistency under different DPI scales 2026-03-02 10:38:06 +08:00
dijunkun
9a5553a636 [chore] update fonts 2026-03-02 10:17:06 +08:00
dijunkun
ef02403da6 [fix] fix incorrect sizing of the online status indicator on high-DPI displays 2026-03-01 16:47:09 +08:00
dijunkun
adfab363c1 [feat] add online presence check before connecting and show offline warning dialog 2026-03-01 16:29:11 +08:00
dijunkun
123d4cf595 [fix] update MiniRTC: fix the macOS hardware decode fail when the server using openH264 encode 2026-03-01 15:40:50 +08:00
dijunkun
19feb8ff49 [feat] show device online/offline status in recent connection tooltip 2026-02-28 17:25:41 +08:00
dijunkun
9223bf9d2d [feat] add online status indicators for recent connections 2026-02-28 17:06:44 +08:00
dijunkun
11b5f87841 [feat] update MiniRTC: add signaling send/receive API support 2026-02-28 17:04:47 +08:00
dijunkun
cea59fb453 [feat] update MiniRTC 2026-02-27 17:53:51 +08:00
ZongYangBigPolo
3a179bf480 [feat] add macOS installer icon and optimize packaging script (#70) 2026-02-27 17:37:54 +08:00
dijunkun
b9c53024f1 [feat] set video quality to HIGH and enable hardware codec by default 2026-02-27 17:24:04 +08:00
dijunkun
62b37ad698 [fix] resolve failures in the WGC→DXGI→GDI fallback chain 2026-02-27 16:33:57 +08:00
dijunkun
de56cd5d3b [feat] update MiniRTC 2026-02-27 16:30:08 +08:00
dijunkun
8d9d78185a [fix] fix issue where client display list was incorrectly merged into the server display list 2026-02-27 16:27:37 +08:00
dijunkun
b10a6512fe [feat] add Windows DXGI/GDI screen capture with WGC→DXGI→GDI fallback support 2026-02-27 13:55:41 +08:00
dijunkun
a94da8802f [feat] make MainWindow and ServerWindow use rounded corners 2026-02-26 18:06:07 +08:00
dijunkun
4e6f82d00c [feat] restrict the dragging range of the ControlWindow 2026-02-26 15:55:04 +08:00
dijunkun
5e2ad99ec0 [feat] update MiniRTC to resolve color distortion in the OpenH264 decoder 2026-02-25 17:48:30 +08:00
dijunkun
8ab50ea362 [feat] add video resolution and conection mode in NetTrafficStats window 2026-02-25 15:33:17 +08:00
dijunkun
25e9958a69 Merge branch 'file-transfer' of https://github.com/kunkundi/crossdesk into file-transfer 2026-02-24 17:56:21 +08:00
dijunkun
410ea8b96b [feat] update MiniRTC 2026-02-24 17:56:02 +08:00
dijunkun
e656664cad [chore] adjust file transfer save path button pos 2026-02-24 14:36:03 +08:00
dijunkun
0e6cee0961 [fix] fix stream window rendering height 2026-02-24 14:31:34 +08:00
dijunkun
42506b8c1d [ci] combine Linux amd64 and arm64 builds into a single job using matrix 2026-02-13 02:31:58 +08:00
dijunkun
e35365d162 [feat] disable and style minimize_to_tray checkbox for non-Windows platforms 2026-02-13 02:29:52 +08:00
dijunkun
bf1c0f796d [fix] fix Linux system certificate loading failure 2026-02-13 01:56:49 +08:00
dijunkun
547532b28c [fix] fix server window scaling issues on high-DPI displays 2026-02-13 01:26:10 +08:00
dijunkun
a91e23abf6 [fix] fix raw pointer issues when closing connections 2026-02-13 01:12:21 +08:00
dijunkun
2b324f636b [fix] fix macOS system certificate loading failure 2026-02-12 22:49:54 +08:00
48 changed files with 5121 additions and 3417 deletions

View File

@@ -15,76 +15,28 @@ env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
jobs:
# Linux amd64
build-linux-amd64:
name: Build on Ubuntu 22.04 amd64
runs-on: ubuntu-22.04
container:
image: crossdesk/ubuntu20.04:latest
options: --user root
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
echo "VERSION_NUM=${VERSION_NUM}" >> $GITHUB_ENV
- name: Set legal Debian version
shell: bash
id: set_deb_version
run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
else
LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
- name: Checkout code
uses: actions/checkout@v4
with:
submodules: recursive
- name: Build CrossDesk
env:
CUDA_PATH: /usr/local/cuda
XMAKE_GLOBALDIR: /data
run: |
ls -la $XMAKE_GLOBALDIR
xmake f --CROSSDESK_VERSION=${LEGAL_VERSION} --USE_CUDA=true --root -y
xmake b -vy --root crossdesk
- name: Package
run: |
chmod +x ./scripts/linux/pkg_amd64.sh
./scripts/linux/pkg_amd64.sh ${LEGAL_VERSION}
- name: Upload artifact
uses: actions/upload-artifact@v4
with:
name: crossdesk-linux-amd64-${{ env.LEGAL_VERSION }}
path: ${{ github.workspace }}/crossdesk-linux-amd64-${{ env.LEGAL_VERSION }}.deb
# Linux arm64
build-linux-arm64:
name: Build on Ubuntu 22.04 arm64
runs-on: ubuntu-22.04-arm
build-linux:
name: Build Linux (${{ matrix.arch }})
runs-on: ${{ matrix.runner }}
strategy:
matrix:
include:
- arch: amd64
runner: ubuntu-22.04
image: crossdesk/ubuntu20.04:latest
package_script: ./scripts/linux/pkg_amd64.sh
- arch: arm64
runner: ubuntu-22.04-arm
image: crossdesk/ubuntu20.04-arm64v8:latest
package_script: ./scripts/linux/pkg_arm64.sh
container:
image: ${{ matrix.image }}
options: --user root
steps:
- name: Extract version number
id: version
run: |
VERSION="${GITHUB_REF##*/}"
VERSION_NUM="${VERSION#v}"
@@ -92,15 +44,16 @@ jobs:
- name: Set legal Debian version
shell: bash
id: set_deb_version
run: |
SHORT_SHA=$(echo "${GITHUB_SHA}" | cut -c1-7)
BUILD_DATE=$(TZ=Asia/Shanghai date +%Y%m%d)
if [[ ! "${VERSION_NUM}" =~ ^[0-9] ]]; then
LEGAL_VERSION="v0.0.0-${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
else
LEGAL_VERSION="v${VERSION_NUM}-${BUILD_DATE}-${SHORT_SHA}"
fi
echo "LEGAL_VERSION=${LEGAL_VERSION}" >> $GITHUB_ENV
echo "BUILD_DATE=${BUILD_DATE}" >> $GITHUB_ENV
@@ -313,7 +266,7 @@ jobs:
name: Publish Release
if: startsWith(github.ref, 'refs/tags/v')
needs:
[build-linux-amd64, build-linux-arm64, build-macos, build-windows-x64]
[build-linux, build-macos, build-windows-x64]
runs-on: ubuntu-latest
steps:

View File

@@ -138,6 +138,39 @@ productbuild \
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}
echo "PKG package created successfully."

View File

@@ -138,6 +138,39 @@ productbuild \
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}
echo "PKG package created successfully."

View File

@@ -0,0 +1,63 @@
#include "rounded_corner_button.h"
namespace crossdesk {
bool RoundedCornerButton(const char* label, const ImVec2& size, float rounding,
ImDrawFlags round_flags, bool enabled,
ImU32 normal_col, ImU32 hover_col, ImU32 active_col,
ImU32 border_col) {
ImGuiWindow* current_window = ImGui::GetCurrentWindow();
if (current_window->SkipItems) return false;
const ImGuiStyle& style = ImGui::GetStyle();
ImGuiID button_id = current_window->GetID(label);
ImVec2 cursor_pos = current_window->DC.CursorPos;
ImVec2 button_size = ImGui::CalcItemSize(size, 0.0f, 0.0f);
ImRect button_rect(cursor_pos, ImVec2(cursor_pos.x + button_size.x,
cursor_pos.y + button_size.y));
ImGui::ItemSize(button_rect);
if (!ImGui::ItemAdd(button_rect, button_id)) return false;
bool is_hovered = false, is_held = false;
bool is_pressed = false;
if (enabled) {
is_pressed =
ImGui::ButtonBehavior(button_rect, button_id, &is_hovered, &is_held);
}
if (normal_col == 0) normal_col = ImGui::GetColorU32(ImGuiCol_Button);
if (hover_col == 0) hover_col = ImGui::GetColorU32(ImGuiCol_ButtonHovered);
if (active_col == 0) active_col = ImGui::GetColorU32(ImGuiCol_ButtonActive);
if (border_col == 0) border_col = ImGui::GetColorU32(ImGuiCol_Border);
ImU32 fill_color = normal_col;
if (is_held && is_hovered)
fill_color = active_col;
else if (is_hovered)
fill_color = hover_col;
if (!enabled) fill_color = IM_COL32(120, 120, 120, 180);
ImDrawList* window_draw_list = ImGui::GetWindowDrawList();
window_draw_list->AddRectFilled(button_rect.Min, button_rect.Max, fill_color,
rounding, round_flags);
if (style.FrameBorderSize > 0.0f) {
window_draw_list->AddRect(button_rect.Min, button_rect.Max, border_col,
rounding, round_flags, style.FrameBorderSize);
}
ImU32 text_color =
ImGui::GetColorU32(enabled ? ImGuiCol_Text : ImGuiCol_TextDisabled);
const char* label_end = ImGui::FindRenderedTextEnd(label);
ImGui::PushStyleColor(ImGuiCol_Text,
ImGui::ColorConvertU32ToFloat4(text_color));
ImGui::RenderTextClipped(button_rect.Min, button_rect.Max, label, label_end,
nullptr, ImVec2(0.5f, 0.5f), &button_rect);
ImGui::PopStyleColor();
return is_pressed;
}
} // namespace crossdesk

View File

@@ -0,0 +1,20 @@
/*
* @Author: DI JUNKUN
* @Date: 2026-02-26
* Copyright (c) 2026 by DI JUNKUN, All Rights Reserved.
*/
#ifndef _ROUNDED_CORNER_BUTTON_H_
#define _ROUNDED_CORNER_BUTTON_H_
#include "imgui.h"
#include "imgui_internal.h"
namespace crossdesk {
bool RoundedCornerButton(const char* label, const ImVec2& size, float rounding,
ImDrawFlags round_flags, bool enabled = true,
ImU32 normal_col = 0, ImU32 hover_col = 0,
ImU32 active_col = 0, ImU32 border_col = 0);
} // namespace crossdesk
#endif

View File

@@ -71,7 +71,7 @@ class ConfigCenter {
const char* section_ = "Settings";
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_ENCODE_FORMAT video_encode_format_ = VIDEO_ENCODE_FORMAT::H264;
bool hardware_video_codec_ = false;

File diff suppressed because it is too large Load Diff

View File

@@ -146,6 +146,8 @@ static std::vector<std::string> p2p_disconnected = {
static std::vector<std::string> p2p_connecting = {
reinterpret_cast<const char*>(u8"正在建立对等连接..."),
"P2P Connecting ..."};
static std::vector<std::string> receiving_screen = {
reinterpret_cast<const char*>(u8"画面接收中..."), "Receiving screen..."};
static std::vector<std::string> p2p_failed = {
reinterpret_cast<const char*>(u8"对等连接失败"), "P2P Failed"};
static std::vector<std::string> p2p_closed = {
@@ -205,11 +207,25 @@ static std::vector<std::string> browse = {
reinterpret_cast<const char*>(u8"浏览"), "Browse"};
static std::vector<std::string> default_desktop = {
reinterpret_cast<const char*>(u8"桌面"), "Desktop"};
#if _WIN32
static std::vector<std::string> minimize_to_tray = {
reinterpret_cast<const char*>(u8"退出时最小化到系统托盘:"),
"Minimize to system tray when exit:"};
static std::vector<std::string> resolution = {
reinterpret_cast<const char*>(u8"分辨率"), "Res"};
static std::vector<std::string> connection_mode = {
reinterpret_cast<const char*>(u8"连接模式"), "Mode"};
static std::vector<std::string> connection_mode_direct = {
reinterpret_cast<const char*>(u8"直连"), "Direct"};
static std::vector<std::string> connection_mode_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
static std::vector<LPCWSTR> exit_program = {L"退出", L"Exit"};
#endif
#ifdef __APPLE__

36
src/gui/device_presence.h Normal file
View 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

View File

@@ -17,7 +17,7 @@ int Render::LocalWindow() {
ImGui::SetNextWindowPos(ImVec2(0.0f, io.DisplaySize.y * TITLE_BAR_HEIGHT),
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::BeginChild("LocalDesktopWindow",
@@ -42,7 +42,7 @@ int Render::LocalWindow() {
ImGuiCond_Always);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(239.0f / 255, 240.0f / 255,
242.0f / 255, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, 10.0f);
ImGui::PushStyleVar(ImGuiStyleVar_ChildRounding, window_rounding_ * 1.5f);
ImGui::BeginChild(
"LocalDesktopPanel",
ImVec2(local_window_width * 0.8f, local_window_height * 0.43f),
@@ -101,7 +101,7 @@ int Render::LocalWindow() {
ImGuiCol_WindowBg,
ImVec4(1.0f, 1.0f, 1.0f, 1.0f - (float)time_duration));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 0.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("ConnectionStatusWindow", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoSavedSettings);
@@ -177,10 +177,11 @@ int Render::LocalWindow() {
ImGui::SetNextWindowSize(
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::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("ResetPasswordWindow", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |

View File

@@ -9,7 +9,7 @@ int Render::RecentConnectionsWindow() {
ImGuiIO& io = ImGui::GetIO();
float recent_connection_window_width = io.DisplaySize.x;
float recent_connection_window_height =
io.DisplaySize.y * (0.46f - STATUS_BAR_HEIGHT);
io.DisplaySize.y * (0.455f - STATUS_BAR_HEIGHT);
ImGui::SetNextWindowPos(ImVec2(0, io.DisplaySize.y * 0.55f),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
@@ -122,6 +122,8 @@ int Render::ShowRecentConnections() {
it.second.remote_host_name = "unknown";
}
bool online = device_presence_.IsOnline(it.second.remote_id);
ImVec2 image_screen_pos = ImVec2(
ImGui::GetCursorScreenPos().x + recent_connection_image_width * 0.04f,
ImGui::GetCursorScreenPos().y + recent_connection_image_height * 0.08f);
@@ -132,6 +134,29 @@ int Render::ShowRecentConnections() {
ImGui::Image(
(ImTextureID)(intptr_t)it.second.texture,
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
{
@@ -155,14 +180,6 @@ int Render::ShowRecentConnections() {
ImGui::Text("%s", it.second.remote_id.c_str());
ImGui::SetWindowFontScale(1.0f);
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));
@@ -242,6 +259,9 @@ int Render::ShowRecentConnections() {
if (show_confirm_delete_connection_) {
ConfirmDeleteConnection();
}
if (show_offline_warning_window_) {
OfflineWarningWindow();
}
return 0;
}
@@ -253,10 +273,10 @@ int Render::ConfirmDeleteConnection() {
ImGui::SetNextWindowSize(
ImVec2(io.DisplaySize.x * 0.33f, io.DisplaySize.y * 0.33f));
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0, 1.0, 1.0, 1.0));
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, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("ConfirmDeleteConnectionWindow", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
@@ -299,4 +319,45 @@ int Render::ConfirmDeleteConnection() {
ImGui::PopStyleVar();
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

View File

@@ -18,7 +18,7 @@ int Render::RemoteWindow() {
ImGui::SetNextWindowPos(
ImVec2(io.DisplaySize.x * 0.5f, io.DisplaySize.y * TITLE_BAR_HEIGHT),
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::BeginChild("RemoteDesktopWindow",
@@ -195,7 +195,10 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
props->control_window_min_width_ = title_bar_height_ * 0.65f;
props->control_window_min_height_ = title_bar_height_ * 1.3f;
props->control_window_max_width_ = title_bar_height_ * 9.0f;
props->control_window_max_height_ = title_bar_height_ * 6.0f;
props->control_window_max_height_ = title_bar_height_ * 7.0f;
props->connection_status_ = ConnectionStatus::Connecting;
show_connection_status_window_ = true;
if (!props->peer_) {
LOG_INFO("Create peer [{}] instance failed", props->local_id_);

View File

@@ -298,7 +298,7 @@ SDL_HitTestResult Render::HitTestCallback(SDL_Window* window,
float mouse_grab_padding = render->title_bar_button_width_ * 0.16f;
if (area->y < render->title_bar_button_width_ &&
area->y > mouse_grab_padding &&
area->x < window_width - render->title_bar_button_width_ * 4.0f &&
area->x < window_width - render->title_bar_button_width_ * 3.0f &&
area->x > mouse_grab_padding) {
return SDL_HITTEST_DRAGGABLE;
}
@@ -810,6 +810,7 @@ int Render::CreateConnectionPeer() {
params_.on_receive_video_frame = OnReceiveVideoBufferCb;
params_.on_signal_status = OnSignalStatusCb;
params_.on_signal_message = OnSignalMessageCb;
params_.on_connection_status = OnConnectionStatusCb;
params_.on_net_status_report = OnNetStatusReport;
@@ -893,7 +894,7 @@ void Render::UpdateInteractions() {
mouse_controller_is_started_ = false;
}
if (start_keyboard_capturer_ && foucs_on_stream_window_) {
if (start_keyboard_capturer_ && focus_on_stream_window_) {
if (!keyboard_capturer_is_started_) {
StartKeyboardCapturer();
keyboard_capturer_is_started_ = true;
@@ -914,9 +915,10 @@ int Render::CreateMainWindow() {
ImGui::SetCurrentContext(main_ctx_);
if (!SDL_CreateWindowAndRenderer(
"Remote Desk", (int)main_window_width_, (int)main_window_height_,
"CrossDesk Main Window", (int)main_window_width_,
(int)main_window_height_,
SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS |
SDL_WINDOW_HIDDEN,
SDL_WINDOW_HIDDEN | SDL_WINDOW_TRANSPARENT,
&main_window_, &main_renderer_)) {
LOG_ERROR("Error creating MainWindow and MainRenderer: {}", SDL_GetError());
return -1;
@@ -936,6 +938,7 @@ int Render::CreateMainWindow() {
(int)(server_window_width_default_ * dpi_scale_);
server_window_normal_height_ =
(int)(server_window_height_default_ * dpi_scale_);
window_rounding_ = window_rounding_default_ * dpi_scale_;
SDL_SetWindowSize(main_window_, (int)main_window_width_,
(int)main_window_height_);
@@ -946,6 +949,8 @@ int Render::CreateMainWindow() {
// for window region action
SDL_SetWindowHitTest(main_window_, HitTestCallback, this);
SDL_SetRenderDrawBlendMode(main_renderer_, SDL_BLENDMODE_BLEND);
SetupFontAndStyle(&main_windows_system_chinese_font_);
ImGuiStyle& style = ImGui::GetStyle();
@@ -1002,9 +1007,10 @@ int Render::CreateStreamWindow() {
ImGui::SetCurrentContext(stream_ctx_);
if (!SDL_CreateWindowAndRenderer(
"Stream window", (int)stream_window_width_,
"CrossDesk Stream Window", (int)stream_window_width_,
(int)stream_window_height_,
SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS,
SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS |
SDL_WINDOW_TRANSPARENT,
&stream_window_, &stream_renderer_)) {
LOG_ERROR("Error creating stream_window_ and stream_renderer_: {}",
SDL_GetError());
@@ -1018,6 +1024,8 @@ int Render::CreateStreamWindow() {
// for window region action
SDL_SetWindowHitTest(stream_window_, HitTestCallback, this);
SDL_SetRenderDrawBlendMode(stream_renderer_, SDL_BLENDMODE_BLEND);
SetupFontAndStyle(&stream_windows_system_chinese_font_);
ImGuiStyle& style = ImGui::GetStyle();
@@ -1052,13 +1060,18 @@ int Render::DestroyStreamWindow() {
if (stream_renderer_) {
SDL_DestroyRenderer(stream_renderer_);
stream_renderer_ = nullptr;
}
if (stream_window_) {
SDL_DestroyWindow(stream_window_);
stream_window_ = nullptr;
}
stream_window_created_ = false;
focus_on_stream_window_ = false;
stream_window_grabbed_ = false;
control_mouse_ = false;
return 0;
}
@@ -1073,12 +1086,12 @@ int Render::CreateServerWindow() {
return -1;
}
ImGui::SetCurrentContext(server_ctx_);
if (!SDL_CreateWindowAndRenderer("Server window", (int)server_window_width_,
(int)server_window_height_,
SDL_WINDOW_HIGH_PIXEL_DENSITY |
SDL_WINDOW_BORDERLESS |
SDL_WINDOW_TRANSPARENT,
&server_window_, &server_renderer_)) {
if (!SDL_CreateWindowAndRenderer(
"CrossDesk Server Window", (int)server_window_width_,
(int)server_window_height_,
SDL_WINDOW_HIGH_PIXEL_DENSITY | SDL_WINDOW_BORDERLESS |
SDL_WINDOW_TRANSPARENT,
&server_window_, &server_renderer_)) {
LOG_ERROR("Error creating server_window_ and server_renderer_: {}",
SDL_GetError());
return -1;
@@ -1160,13 +1173,16 @@ int Render::DestroyServerWindow() {
if (server_renderer_) {
SDL_DestroyRenderer(server_renderer_);
server_renderer_ = nullptr;
}
if (server_window_) {
SDL_DestroyWindow(server_window_);
server_window_ = nullptr;
}
server_window_created_ = false;
server_window_inited_ = false;
return 0;
}
@@ -1290,6 +1306,8 @@ int Render::DrawMainWindow() {
ImGuiIO& io = ImGui::GetIO();
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::SetNextWindowSize(ImVec2(io.DisplaySize.x, io.DisplaySize.y),
ImGuiCond_Always);
@@ -1298,6 +1316,7 @@ int Render::DrawMainWindow() {
ImGuiWindowFlags_NoBringToFrontOnFocus |
ImGuiWindowFlags_NoDocking);
ImGui::PopStyleColor();
ImGui::PopStyleVar();
TitleBar(true);
@@ -1318,6 +1337,7 @@ int Render::DrawMainWindow() {
ImGui::Render();
SDL_SetRenderScale(main_renderer_, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_SetRenderDrawColor(main_renderer_, 0, 0, 0, 0);
SDL_RenderClear(main_renderer_);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), main_renderer_);
SDL_RenderPresent(main_renderer_);
@@ -1367,6 +1387,7 @@ int Render::DrawStreamWindow() {
ImGui::Render();
SDL_SetRenderScale(stream_renderer_, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_SetRenderDrawColor(stream_renderer_, 0, 0, 0, 255);
SDL_RenderClear(stream_renderer_);
// std::shared_lock lock(client_properties_mutex_);
@@ -1393,24 +1414,20 @@ int Render::DrawServerWindow() {
LOG_ERROR("Server context is null");
return -1;
}
if (server_window_) {
int w = 0;
int h = 0;
SDL_GetWindowSize(server_window_, &w, &h);
if (w > 0 && h > 0) {
server_window_width_ = (float)w;
server_window_height_ = (float)h;
}
}
ImGui::SetCurrentContext(server_ctx_);
ImGui_ImplSDLRenderer3_NewFrame();
ImGui_ImplSDL3_NewFrame();
ImGui::NewFrame();
ImGuiIO& io = ImGui::GetIO();
server_window_width_ = io.DisplaySize.x;
server_window_height_ = io.DisplaySize.y;
ServerWindow();
ImGui::Render();
SDL_SetRenderDrawColor(server_renderer_, 255, 255, 255, 255);
SDL_SetRenderScale(server_renderer_, io.DisplayFramebufferScale.x,
io.DisplayFramebufferScale.y);
SDL_SetRenderDrawColor(server_renderer_, 0, 0, 0, 0);
SDL_RenderClear(server_renderer_);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), server_renderer_);
SDL_RenderPresent(server_renderer_);
@@ -1532,7 +1549,8 @@ void Render::InitializeModules() {
std::shared_lock lock(client_properties_mutex_);
int ret = -1;
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,
props->clipboard_label_.c_str());
if (ret != 0) {
@@ -1586,6 +1604,7 @@ void Render::MainLoop() {
UpdateLabels();
HandleRecentConnections();
HandleConnectionStatusChange();
HandleStreamWindow();
HandleServerWindow();
@@ -1625,10 +1644,46 @@ void Render::HandleRecentConnections() {
LOG_INFO("Load recent connection thumbnails");
}
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() {
if (need_to_create_stream_window_) {
CreateStreamWindow();
@@ -2051,7 +2106,8 @@ void Render::UpdateRenderRect() {
(int)(render_area_width * video_ratio_reverse)};
} else if (render_area_width > render_area_height * video_ratio) {
props->stream_render_rect_ = {
(int)abs(render_area_width - render_area_height * video_ratio) / 2,
(int)abs(render_area_width - render_area_height * video_ratio) / 2 +
(int)props->render_window_x_,
(int)props->render_window_y_, (int)(render_area_height * video_ratio),
(int)render_area_height};
} else {
@@ -2176,7 +2232,7 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
case SDL_EVENT_WINDOW_FOCUS_GAINED:
if (stream_window_ &&
SDL_GetWindowID(stream_window_) == event.window.windowID) {
foucs_on_stream_window_ = true;
focus_on_stream_window_ = true;
} else if (main_window_ &&
SDL_GetWindowID(main_window_) == event.window.windowID) {
foucs_on_main_window_ = true;
@@ -2186,7 +2242,7 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
case SDL_EVENT_WINDOW_FOCUS_LOST:
if (stream_window_ &&
SDL_GetWindowID(stream_window_) == event.window.windowID) {
foucs_on_stream_window_ = false;
focus_on_stream_window_ = false;
} else if (main_window_ &&
SDL_GetWindowID(main_window_) == event.window.windowID) {
foucs_on_main_window_ = false;
@@ -2200,7 +2256,7 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
case SDL_EVENT_MOUSE_BUTTON_DOWN:
case SDL_EVENT_MOUSE_BUTTON_UP:
case SDL_EVENT_MOUSE_WHEEL:
if (foucs_on_stream_window_) {
if (focus_on_stream_window_) {
ProcessMouseEvent(event);
}
break;

View File

@@ -25,6 +25,7 @@
#include "IconsFontAwesome6.h"
#include "config_center.h"
#include "device_controller_factory.h"
#include "device_presence.h"
#include "imgui.h"
#include "imgui_impl_sdl3.h"
#include "imgui_impl_sdlrenderer3.h"
@@ -94,7 +95,7 @@ class Render {
bool connection_established_ = false;
bool rejoin_ = 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 audio_capture_button_pressed_ = true;
bool control_mouse_ = true;
@@ -192,6 +193,7 @@ class Render {
void UpdateLabels();
void UpdateInteractions();
void HandleRecentConnections();
void HandleConnectionStatusChange();
void HandleStreamWindow();
void HandleServerWindow();
void Cleanup();
@@ -255,9 +257,12 @@ class Render {
int DrawStreamWindow();
int DrawServerWindow();
int ConfirmDeleteConnection();
int OfflineWarningWindow();
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
void DrawConnectionStatusText(
std::shared_ptr<SubStreamWindowProperties>& props);
void DrawReceivingScreenText(
std::shared_ptr<SubStreamWindowProperties>& props);
#ifdef __APPLE__
int RequestPermissionWindow();
bool CheckScreenRecordingPermission();
@@ -286,6 +291,9 @@ class Render {
static void OnSignalStatusCb(SignalStatus status, const char* user_id,
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,
size_t user_id_size, void* user_data);
@@ -402,9 +410,12 @@ class Render {
// recent connections
std::vector<std::pair<std::string, Thumbnail::RecentConnection>>
recent_connections_;
std::vector<std::string> recent_connection_ids_;
int recent_connection_image_width_ = 160;
int recent_connection_image_height_ = 90;
uint32_t recent_connection_image_save_time_ = 0;
DevicePresence device_presence_;
bool need_to_send_recent_connections_ = true;
// main window render
SDL_Window* main_window_ = nullptr;
@@ -430,11 +441,11 @@ class Render {
bool screen_capturer_is_started_ = false;
bool start_speaker_capturer_ = false;
bool speaker_capturer_is_started_ = false;
bool start_keyboard_capturer_ = true;
bool start_keyboard_capturer_ = false;
bool show_cursor_ = false;
bool keyboard_capturer_is_started_ = false;
bool foucs_on_main_window_ = false;
bool foucs_on_stream_window_ = false;
bool focus_on_stream_window_ = false;
bool main_window_minimized_ = false;
uint32_t last_main_minimize_request_tick_ = 0;
uint32_t last_stream_minimize_request_tick_ = 0;
@@ -534,6 +545,8 @@ class Render {
int server_window_normal_height_ = 150;
float server_window_dpi_scaling_w_ = 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
bool server_window_collapsed_ = false;
@@ -569,9 +582,11 @@ class Render {
bool is_server_mode_ = false;
bool reload_recent_connections_ = true;
bool show_confirm_delete_connection_ = false;
bool show_offline_warning_window_ = false;
bool delete_connection_ = false;
bool is_tab_bar_hovered_ = false;
std::string delete_connection_name_ = "";
std::string offline_warning_text_ = "";
bool re_enter_remote_id_ = false;
double copy_start_time_ = 0;
SignalStatus signal_status_ = SignalStatus::SignalClosed;
@@ -622,10 +637,10 @@ class Render {
char self_hosted_id_[17] = "";
char self_hosted_user_id_[17] = "";
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_encode_format_button_value_ = 0;
bool enable_hardware_video_codec_ = false;
bool enable_hardware_video_codec_ = true;
bool enable_turn_ = true;
bool enable_srtp_ = false;
char signal_server_ip_[256] = "api.crossdesk.cn";
@@ -677,4 +692,4 @@ class Render {
FileTransferState file_transfer_;
};
} // namespace crossdesk
#endif
#endif

View File

@@ -6,6 +6,7 @@
#include <fstream>
#include <limits>
#include <unordered_map>
#include <unordered_set>
#include "clipboard.h"
#include "device_controller.h"
@@ -20,6 +21,47 @@
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) {
RemoteAction remote_action;
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;
if (!controlled_remote_id_.empty()) {
// std::shared_lock lock(client_properties_mutex_);
if (client_properties_.find(controlled_remote_id_) !=
client_properties_.end()) {
auto props = client_properties_[controlled_remote_id_];
if (props->connection_status_ == ConnectionStatus::Connected) {
std::string target_id = controlled_remote_id_.empty() ? focused_remote_id_
: controlled_remote_id_;
if (!target_id.empty()) {
if (client_properties_.find(target_id) != client_properties_.end()) {
auto props = client_properties_[target_id];
if (props->connection_status_ == ConnectionStatus::Connected &&
props->peer_) {
std::string msg = remote_action.to_json();
if (props->peer_) {
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
}
}
@@ -104,11 +145,10 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
}
if (props->control_bar_hovered_ || props->display_selectable_hovered_) {
remote_action.m.flag = MouseFlag::move;
break;
}
std::string msg = remote_action.to_json();
if (props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
@@ -154,8 +194,11 @@ int Render::ProcessMouseEvent(const SDL_Event& event) {
(float)(last_mouse_event.button.y - props->stream_render_rect_.y) /
render_height;
std::string msg = remote_action.to_json();
if (props->control_bar_hovered_) {
continue;
}
if (props->peer_) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
}
@@ -352,6 +395,13 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
return;
} else if (source_id == render->clipboard_label_) {
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);
if (!Clipboard::SetText(clipboard_text)) {
LOG_ERROR("Failed to set clipboard content from remote");
@@ -566,13 +616,6 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
remote_action.i.host_name, remote_action.i.host_name_size);
LOG_INFO("Remote hostname: [{}]",
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);
}
} else {
@@ -612,6 +655,7 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
render->signal_connected_ = false;
} else if (SignalStatus::SignalConnected == status) {
render->signal_connected_ = true;
render->need_to_send_recent_connections_ = true;
LOG_INFO("[{}] connected to signal server", client_id);
} else if (SignalStatus::SignalFailed == status) {
render->signal_connected_ = false;
@@ -721,13 +765,14 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
0, (int)render->title_bar_height_,
(int)render->stream_window_width_,
(int)(render->stream_window_height_ - render->title_bar_height_)};
render->start_keyboard_capturer_ = true;
break;
}
case ConnectionStatus::Disconnected:
case ConnectionStatus::Failed:
case ConnectionStatus::Closed: {
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_);
@@ -745,6 +790,8 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
event.user.data1 = props.get();
SDL_PushEvent(&event);
render->focus_on_stream_window_ = false;
break;
}
case ConnectionStatus::IncorrectPassword: {
@@ -824,12 +871,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
render->start_screen_capturer_ = true;
render->start_speaker_capturer_ = true;
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;
#endif
if (std::all_of(render->connection_status_.begin(),
render->connection_status_.end(), [](const auto& kv) {
return kv.first.find("web") != std::string::npos;
@@ -839,6 +881,8 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
break;
}
case ConnectionStatus::Disconnected:
case ConnectionStatus::Failed:
case ConnectionStatus::Closed: {
if (std::all_of(render->connection_status_.begin(),
render->connection_status_.end(), [](const auto& kv) {
@@ -860,6 +904,10 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_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(),

View File

@@ -138,10 +138,10 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
float line_padding = title_bar_height_ * 0.12f;
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_) {
ImGui::SetCursorPosX(props->is_control_bar_in_left_
? props->control_window_width_ * 1.03f
? props->control_window_width_ * 0.03f
: props->control_window_width_ * 0.17f);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
@@ -198,24 +198,21 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
float mouse_y = ImGui::GetCursorScreenPos().y;
float disable_mouse_x = mouse_x + line_padding;
float disable_mouse_y = mouse_y + line_padding;
std::string mouse = props->mouse_control_button_pressed_
? ICON_FA_COMPUTER_MOUSE
: ICON_FA_COMPUTER_MOUSE;
std::string mouse = ICON_FA_COMPUTER_MOUSE;
ImGui::SetWindowFontScale(0.5f);
if (ImGui::Button(mouse.c_str(), ImVec2(button_width, button_height))) {
if (props->connection_established_) {
start_keyboard_capturer_ = !start_keyboard_capturer_;
props->control_mouse_ = !props->control_mouse_;
props->mouse_control_button_pressed_ =
!props->mouse_control_button_pressed_;
props->enable_mouse_control_ = !props->enable_mouse_control_;
props->mouse_control_button_label_ =
props->mouse_control_button_pressed_
props->enable_mouse_control_
? localization::release_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),
ImVec2(mouse_x + button_width - line_padding,
mouse_y + button_height - line_padding),
@@ -359,13 +356,11 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::SameLine();
}
float expand_button_pos_x =
props->control_bar_expand_ ? (props->is_control_bar_in_left_
? props->control_window_width_ * 1.917f
: props->control_window_width_ * 0.03f)
: (props->is_control_bar_in_left_
? props->control_window_width_ * 1.02f
: props->control_window_width_ * 0.23f);
float expand_button_pos_x = props->control_bar_expand_
? (props->is_control_bar_in_left_
? props->control_window_width_ * 0.917f
: props->control_window_width_ * 0.03f)
: props->control_window_width_ * 0.11f;
ImGui::SetCursorPosX(expand_button_pos_x);
@@ -397,9 +392,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
}
int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
ImGui::SetCursorPos(ImVec2(props->is_control_bar_in_left_
? props->control_window_width_ * 1.02f
: props->control_window_width_ * 0.02f,
ImGui::SetCursorPos(ImVec2(props->control_window_width_ * 0.048f,
props->control_window_min_height_));
ImGui::SetWindowFontScale(0.5f);
if (ImGui::BeginTable("NetTrafficStats", 4, ImGuiTableFlags_BordersH,
@@ -460,12 +453,33 @@ int Render::NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props) {
LossRateDisplay(props->net_traffic_stats_.total_inbound_stats.loss_rate);
ImGui::TableNextColumn();
ImGui::Text("FPS");
ImGui::Text("FPS:");
ImGui::TableNextColumn();
ImGui::Text("%d", props->fps_);
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::Text("%s:",
localization::resolution[localization_language_index_].c_str());
ImGui::TableNextColumn();
ImGui::Text("%dx%d", props->video_width_, props->video_height_);
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::TableNextColumn();
ImGui::Text(
"%s:",
localization::connection_mode[localization_language_index_].c_str());
ImGui::TableNextColumn();
ImGui::Text(
"%s",
props->traversal_mode_ == 0
? localization::connection_mode_direct[localization_language_index_]
.c_str()
: localization::connection_mode_relay[localization_language_index_]
.c_str());
ImGui::EndTable();
}
ImGui::SetWindowFontScale(1.0f);

View File

@@ -9,25 +9,27 @@ int Render::StatusBar() {
float status_bar_width = io.DisplaySize.x;
float status_bar_height = io.DisplaySize.y * STATUS_BAR_HEIGHT;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e;
ImGui::SetNextWindowPos(ImVec2(0, io.DisplaySize.y * (1 - STATUS_BAR_HEIGHT)),
ImGuiCond_Always);
ImGui::BeginChild(
"StatusBar", ImVec2(status_bar_width, status_bar_height),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::BeginChild("StatusBar", ImVec2(status_bar_width, status_bar_height),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleColor(2);
ImVec2 dot_pos = ImVec2(status_bar_width * 0.025f,
io.DisplaySize.y * (1 - STATUS_BAR_HEIGHT * 0.5f));
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,
ImColor(signal_connected_ ? 0.0f : 1.0f,
signal_connected_ ? 1.0f : 0.0f, 0.0f),
100);
draw_list->AddCircle(dot_pos, status_bar_height * 0.25f,
ImColor(1.0f, 1.0f, 1.0f), 100);
ImGui::SetWindowFontScale(0.6f);
draw_list->AddText(
@@ -40,7 +42,6 @@ int Render::StatusBar() {
.c_str());
ImGui::SetWindowFontScale(1.0f);
ImGui::PopStyleColor();
ImGui::EndChild();
return 0;
}

View File

@@ -2,6 +2,7 @@
#include "localization.h"
#include "rd_log.h"
#include "render.h"
#include "rounded_corner_button.h"
constexpr double kNewVersionIconBlinkIntervalSec = 2.0;
constexpr double kNewVersionIconBlinkOnTimeSec = 1.0;
@@ -23,7 +24,7 @@ int Render::TitleBar(bool main_window) {
if (io.DisplaySize.x > 0.0f && io.DisplaySize.y > 0.0f) {
title_bar_width = io.DisplaySize.x;
title_bar_height = io.DisplaySize.y * TITLE_BAR_HEIGHT;
title_bar_height_padding = io.DisplaySize.y * (TITLE_BAR_HEIGHT + 0.01f);
title_bar_height_padding = io.DisplaySize.y * TITLE_BAR_HEIGHT;
title_bar_button_width = io.DisplaySize.x * TITLE_BAR_BUTTON_WIDTH;
title_bar_button_height = io.DisplaySize.y * TITLE_BAR_BUTTON_HEIGHT;
@@ -45,7 +46,7 @@ int Render::TitleBar(bool main_window) {
title_bar_button_height = title_bar_button_height_;
}
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::BeginChild(main_window ? "MainTitleBar" : "StreamTitleBar",
@@ -285,8 +286,20 @@ int Render::TitleBar(bool main_window) {
float xmark_pos_y = title_bar_button_height * 0.5f;
float xmark_size = title_bar_button_width * 0.33f;
std::string close_button = "##xmark"; // ICON_FA_XMARK;
if (ImGui::Button(close_button.c_str(),
ImVec2(title_bar_button_width, title_bar_button_height))) {
bool close_button_clicked = false;
if (main_window) {
close_button_clicked = RoundedCornerButton(
close_button.c_str(),
ImVec2(title_bar_button_width, title_bar_button_height), 8.5f,
ImDrawFlags_RoundCornersTopRight, true, IM_COL32(0, 0, 0, 0),
IM_COL32(250, 0, 0, 255), IM_COL32(255, 0, 0, 128));
} else {
close_button_clicked =
ImGui::Button(close_button.c_str(),
ImVec2(title_bar_button_width, title_bar_button_height));
}
if (close_button_clicked) {
#if _WIN32
if (enable_minimize_to_tray_) {
tray_->MinimizeToTray();
@@ -299,6 +312,7 @@ int Render::TitleBar(bool main_window) {
}
#endif
}
draw_list->AddLine(ImVec2(xmark_pos_x - xmark_size / 2 - 0.25f,
xmark_pos_y - xmark_size / 2 + 0.75f),
ImVec2(xmark_pos_x + xmark_size / 2 - 1.5f,

View File

@@ -77,8 +77,8 @@ int Render::AboutWindow() {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::SetWindowFontScale(0.5f);
ImGui::Begin(
localization::about[localization_language_index_].c_str(), nullptr,

View File

@@ -15,10 +15,10 @@ bool Render::ConnectionStatusWindow(
ImGui::SetNextWindowSize(
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::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("ConnectionStatusWindow", nullptr,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
@@ -36,6 +36,18 @@ bool Render::ConnectionStatusWindow(
text = localization::p2p_connecting[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);
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_) {
text = localization::p2p_connected[localization_language_index_];
ImGui::SetCursorPosX(connection_status_window_width * 0.43f);

View File

@@ -42,50 +42,74 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
}
}
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1, 1, 1, 1));
ImGui::PushStyleVar(ImGuiStyleVar_WindowMinSize, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 10.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(0, 0, 0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_ * 1.5f);
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::SetNextWindowSize(
ImVec2(props->control_window_width_, props->control_window_height_),
ImGuiCond_Always);
float y_boundary = fullscreen_button_pressed_ ? 0.0f : title_bar_height_;
float container_x = 0.0f;
float container_y = y_boundary;
float container_w = stream_window_width_;
float container_h = stream_window_height_ - y_boundary;
ImGui::SetNextWindowPos(ImVec2(0, title_bar_height_), ImGuiCond_Once);
ImGui::SetNextWindowSize(ImVec2(container_w, container_h), ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(container_x, container_y), ImGuiCond_Always);
float pos_x = 0;
float pos_y = 0;
float y_boundary = fullscreen_button_pressed_ ? 0 : title_bar_height_;
if (props->reset_control_bar_pos_) {
float new_cursor_pos_x = 0;
float new_cursor_pos_y = 0;
std::string container_window_title =
props->remote_id_ + "ControlContainerWindow";
ImGui::Begin(container_window_title.c_str(), nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking |
ImGuiWindowFlags_NoBackground);
ImGui::PopStyleVar();
ImVec2 container_pos = ImGui::GetWindowPos();
if (ImGui::IsMouseDown(ImGuiMouseButton_Left) &&
props->control_bar_hovered_) {
float current_x_rel = props->control_window_pos_.x - container_pos.x;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
ImVec2 delta = ImGui::GetIO().MouseDelta;
pos_x = current_x_rel + delta.x;
pos_y = current_y_rel + delta.y;
if (pos_x < 0.0f) pos_x = 0.0f;
if (pos_y < 0.0f) pos_y = 0.0f;
if (pos_x + props->control_window_width_ > container_w)
pos_x = container_w - props->control_window_width_;
if (pos_y + props->control_window_height_ > container_h)
pos_y = container_h - props->control_window_height_;
} else if (props->reset_control_bar_pos_) {
float new_cursor_pos_x = 0.0f;
float new_cursor_pos_y = 0.0f;
// set control window pos
if (props->control_window_pos_.y + props->control_window_height_ >
stream_window_height_) {
pos_y = stream_window_height_ - props->control_window_height_;
} else if (props->control_window_pos_.y < y_boundary) {
pos_y = y_boundary;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
if (current_y_rel + props->control_window_height_ > container_h) {
pos_y = container_h - props->control_window_height_;
} else if (current_y_rel < 0.0f) {
pos_y = 0.0f;
} else {
pos_y = props->control_window_pos_.y;
pos_y = current_y_rel;
}
if (props->is_control_bar_in_left_) {
pos_x = 0;
pos_x = 0.0f;
} else {
pos_x = stream_window_width_ - props->control_window_width_;
pos_x = container_w - props->control_window_width_;
}
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
if (0 != props->mouse_diff_control_bar_pos_x_ &&
0 != props->mouse_diff_control_bar_pos_y_) {
// set cursor pos
new_cursor_pos_x = pos_x + props->mouse_diff_control_bar_pos_x_;
new_cursor_pos_y = pos_y + props->mouse_diff_control_bar_pos_y_;
new_cursor_pos_x =
container_pos.x + pos_x + props->mouse_diff_control_bar_pos_x_;
new_cursor_pos_y =
container_pos.y + pos_y + props->mouse_diff_control_bar_pos_y_;
SDL_WarpMouseInWindow(stream_window_, (int)new_cursor_pos_x,
(int)new_cursor_pos_y);
@@ -94,12 +118,14 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
} else if (!props->reset_control_bar_pos_ &&
ImGui::IsMouseReleased(ImGuiMouseButton_Left) ||
props->control_window_width_is_changing_) {
if (props->control_window_pos_.x <= stream_window_width_ * 0.5f) {
if (props->control_window_pos_.y + props->control_window_height_ >
stream_window_height_) {
pos_y = stream_window_height_ - props->control_window_height_;
float current_x_rel = props->control_window_pos_.x - container_pos.x;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
if (current_x_rel <= container_w * 0.5f) {
pos_x = 0.0f;
if (current_y_rel + props->control_window_height_ > container_h) {
pos_y = container_h - props->control_window_height_;
} else {
pos_y = props->control_window_pos_.y;
pos_y = current_y_rel;
}
if (props->control_bar_expand_) {
@@ -118,47 +144,53 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
}
}
props->is_control_bar_in_left_ = true;
} else if (props->control_window_pos_.x > stream_window_width_ * 0.5f) {
pos_x = 0;
pos_y =
(props->control_window_pos_.y >= y_boundary &&
props->control_window_pos_.y <=
stream_window_height_ - props->control_window_height_)
? props->control_window_pos_.y
: (props->control_window_pos_.y <
(fullscreen_button_pressed_ ? 0 : title_bar_height_)
? (fullscreen_button_pressed_ ? 0 : title_bar_height_)
: (stream_window_height_ - props->control_window_height_));
} else if (current_x_rel > container_w * 0.5f) {
pos_x = container_w - props->control_window_width_;
pos_y = (current_y_rel >= 0.0f &&
current_y_rel <= container_h - props->control_window_height_)
? current_y_rel
: (current_y_rel < 0.0f
? 0.0f
: (container_h - props->control_window_height_));
if (props->control_bar_expand_) {
if (props->control_window_width_ >= props->control_window_max_width_) {
props->control_window_width_ = props->control_window_max_width_;
props->control_window_width_is_changing_ = false;
pos_x = stream_window_width_ - props->control_window_max_width_;
pos_x = container_w - props->control_window_max_width_;
} else {
props->control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - props->control_window_width_;
pos_x = container_w - props->control_window_width_;
}
} else {
if (props->control_window_width_ <= props->control_window_min_width_) {
props->control_window_width_ = props->control_window_min_width_;
props->control_window_width_is_changing_ = false;
pos_x = stream_window_width_ - props->control_window_min_width_;
pos_x = container_w - props->control_window_min_width_;
} else {
props->control_window_width_is_changing_ = true;
pos_x = stream_window_width_ - props->control_window_width_;
pos_x = container_w - props->control_window_width_;
}
}
props->is_control_bar_in_left_ = false;
}
if (props->control_window_pos_.y + props->control_window_height_ >
stream_window_height_) {
pos_y = stream_window_height_ - props->control_window_height_;
} else if (props->control_window_pos_.y < y_boundary) {
pos_y = y_boundary;
if (current_y_rel + props->control_window_height_ > container_h) {
pos_y = container_h - props->control_window_height_;
} else if (current_y_rel < 0.0f) {
pos_y = 0.0f;
}
ImGui::SetNextWindowPos(ImVec2(pos_x, pos_y), ImGuiCond_Always);
} else {
float current_x_rel = props->control_window_pos_.x - container_pos.x;
float current_y_rel = props->control_window_pos_.y - container_pos.y;
pos_x = current_x_rel;
pos_y = current_y_rel;
if (pos_x < 0.0f) pos_x = 0.0f;
if (pos_y < 0.0f) pos_y = 0.0f;
if (pos_x + props->control_window_width_ > container_w)
pos_x = container_w - props->control_window_width_;
if (pos_y + props->control_window_height_ > container_h)
pos_y = container_h - props->control_window_height_;
}
if (props->control_bar_expand_ && props->control_window_height_is_changing_) {
@@ -180,10 +212,20 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
}
std::string control_window_title = props->remote_id_ + "ControlWindow";
ImGui::Begin(control_window_title.c_str(), nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoDocking);
ImGui::PopStyleVar();
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e;
float child_cursor_x = pos_x;
float child_cursor_y = pos_y;
ImGui::SetCursorPos(ImVec2(child_cursor_x, child_cursor_y));
std::string control_child_window_title =
props->remote_id_ + "ControlChildWindow";
ImGui::BeginChild(
control_child_window_title.c_str(),
ImVec2(props->control_window_width_, props->control_window_height_),
ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleColor();
props->control_window_pos_ = ImGui::GetWindowPos();
SDL_GetMouseState(&props->mouse_pos_x_, &props->mouse_pos_y_);
@@ -192,31 +234,26 @@ int Render::ControlWindow(std::shared_ptr<SubStreamWindowProperties>& props) {
props->mouse_diff_control_bar_pos_y_ =
props->mouse_pos_y_ - props->control_window_pos_.y;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
static bool a, b, c, d, e;
ImGui::SetNextWindowPos(
ImVec2(props->is_control_bar_in_left_
? props->control_window_pos_.x - props->control_window_width_
: props->control_window_pos_.x,
props->control_window_pos_.y),
ImGuiCond_Always);
std::string control_child_window_title =
props->remote_id_ + "ControlChildWindow";
ImGui::BeginChild(control_child_window_title.c_str(),
ImVec2(props->control_window_width_ * 2.0f,
props->control_window_height_),
ImGuiChildFlags_Border, ImGuiWindowFlags_NoDecoration);
ImGui::PopStyleColor();
if (props->control_window_pos_.y < container_pos.y ||
props->control_window_pos_.y + props->control_window_height_ >
(container_pos.y + container_h) ||
props->control_window_pos_.x < container_pos.x ||
props->control_window_pos_.x + props->control_window_width_ >
(container_pos.x + container_w)) {
ImGui::ClearActiveID();
props->reset_control_bar_pos_ = true;
props->mouse_diff_control_bar_pos_x_ = 0;
props->mouse_diff_control_bar_pos_y_ = 0;
}
ControlBar(props);
props->control_bar_hovered_ = ImGui::IsWindowHovered();
ImGui::EndChild();
ImGui::End();
ImGui::PopStyleVar(4);
ImGui::PopStyleVar(3);
ImGui::PopStyleColor();
return 0;
}
} // namespace crossdesk
} // namespace crossdesk

View File

@@ -94,7 +94,7 @@ int Render::FileTransferWindow(
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::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));

View File

@@ -50,8 +50,8 @@ int Render::SettingWindow() {
int settings_items_offset = 0;
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(localization::settings[localization_language_index_].c_str(),
nullptr,
@@ -331,10 +331,14 @@ int Render::SettingWindow() {
ImGui::EndTooltip();
}
}
#if _WIN32
ImGui::Separator();
{
#ifndef _WIN32
ImGui::BeginDisabled();
ImGui::PushStyleColor(ImGuiCol_Text, ImVec4(0.5f, 0.5f, 0.5f, 1.0f));
#endif
settings_items_offset += settings_items_padding;
ImGui::SetCursorPosY(settings_items_offset);
ImGui::AlignTextToFramePadding();
@@ -350,8 +354,11 @@ int Render::SettingWindow() {
ImGui::Checkbox("##enable_minimize_to_tray_",
&enable_minimize_to_tray_);
}
#ifndef _WIN32
ImGui::PopStyleColor();
ImGui::EndDisabled();
#endif
}
ImGui::Separator();
@@ -365,7 +372,7 @@ int Render::SettingWindow() {
.c_str());
ImGui::SameLine();
if (ConfigCenter::LANGUAGE::CHINESE == localization_language_) {
ImGui::SetCursorPosX(title_bar_button_width_ * 2.8f);
ImGui::SetCursorPosX(title_bar_button_width_ * 2.82f);
} else {
ImGui::SetCursorPosX(title_bar_button_width_ * 4.3f);
}

View File

@@ -14,7 +14,8 @@ int Render::MainWindow() {
ImGui::SetNextWindowPos(ImVec2(0.0f, io.DisplaySize.y * (TITLE_BAR_HEIGHT)),
ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Border, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::BeginChild(
"DeskWindow",
ImVec2(local_remote_window_width, local_remote_window_height),
@@ -22,7 +23,7 @@ int Render::MainWindow() {
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::PopStyleColor(2);
LocalWindow();

View File

@@ -127,8 +127,8 @@ int Render::RequestPermissionWindow() {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
ImGui::Begin(

View File

@@ -51,8 +51,8 @@ int Render::SelfHostedServerWindow() {
{
ImGui::SetWindowFontScale(0.5f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(localization::self_hosted_server_settings
[localization_language_index_]

View File

@@ -8,6 +8,7 @@
#include "localization.h"
#include "rd_log.h"
#include "render.h"
#include "rounded_corner_button.h"
namespace crossdesk {
@@ -48,17 +49,19 @@ int Render::ServerWindow() {
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::Begin("##server_window", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse);
ImGui::PopStyleVar();
server_window_title_bar_height_ = title_bar_height_;
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 0.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::BeginChild(
"ServerTitleBar",
@@ -83,9 +86,13 @@ int Render::ServerWindow() {
const char* icon =
server_window_collapsed_ ? ICON_FA_ANGLE_DOWN : ICON_FA_ANGLE_UP;
std::string toggle_label = std::string(icon) + "##server_toggle";
if (ImGui::Button(toggle_label.c_str(),
ImVec2(server_title_bar_button_width,
server_title_bar_button_height))) {
bool toggle_clicked = RoundedCornerButton(
toggle_label.c_str(),
ImVec2(server_title_bar_button_width, server_title_bar_button_height),
8.5f, ImDrawFlags_RoundCornersTopLeft, true, IM_COL32(0, 0, 0, 0),
IM_COL32(0, 0, 0, 25), IM_COL32(255, 255, 255, 255));
if (toggle_clicked) {
if (server_window_) {
int w = 0;
int h = 0;
@@ -114,7 +121,7 @@ int Render::ServerWindow() {
}
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::PopStyleVar(2);
ImGui::PopStyleColor();
RemoteClientInfoWindow();
@@ -141,33 +148,38 @@ int Render::RemoteClientInfoWindow() {
float font_scale = localization_language_index_ == 0 ? 0.5f : 0.45f;
std::vector<std::string> remote_hostnames;
remote_hostnames.reserve(connection_host_names_.size());
for (const auto& kv : connection_host_names_) {
remote_hostnames.push_back(kv.second);
std::vector<std::pair<std::string, std::string>> remote_entries;
remote_entries.reserve(connection_status_.size());
for (const auto& kv : connection_status_) {
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 =
[this](const std::string& hostname) -> std::string {
for (const auto& kv : connection_host_names_) {
if (kv.second == hostname) {
return kv.first;
auto find_display_name_by_remote_id =
[&remote_entries](const std::string& remote_id) -> std::string {
for (const auto& entry : remote_entries) {
if (entry.first == remote_id) {
return entry.second;
}
}
return {};
};
if (!selected_server_remote_hostname_.empty()) {
if (std::find(remote_hostnames.begin(), remote_hostnames.end(),
selected_server_remote_hostname_) == remote_hostnames.end()) {
selected_server_remote_hostname_.clear();
selected_server_remote_id_.clear();
}
if (!selected_server_remote_id_.empty() &&
find_display_name_by_remote_id(selected_server_remote_id_).empty()) {
selected_server_remote_id_.clear();
selected_server_remote_hostname_.clear();
}
if (selected_server_remote_hostname_.empty() && !remote_hostnames.empty()) {
selected_server_remote_hostname_ = remote_hostnames.front();
selected_server_remote_id_ =
find_remote_id_by_hostname(selected_server_remote_hostname_);
if (selected_server_remote_id_.empty() && !remote_entries.empty()) {
selected_server_remote_id_ = remote_entries.front().first;
}
if (!selected_server_remote_id_.empty()) {
selected_server_remote_hostname_ =
find_display_name_by_remote_id(selected_server_remote_id_);
}
ImGui::SetWindowFontScale(font_scale);
@@ -189,13 +201,12 @@ int Render::RemoteClientInfoWindow() {
ImGui::AlignTextToFramePadding();
if (ImGui::BeginCombo("##server_remote_id", selected_preview)) {
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 =
(remote_hostnames[i] == selected_server_remote_hostname_);
if (ImGui::Selectable(remote_hostnames[i].c_str(), selected)) {
selected_server_remote_hostname_ = remote_hostnames[i];
selected_server_remote_id_ =
find_remote_id_by_hostname(selected_server_remote_hostname_);
(remote_entries[i].first == selected_server_remote_id_);
if (ImGui::Selectable(remote_entries[i].second.c_str(), selected)) {
selected_server_remote_id_ = remote_entries[i].first;
selected_server_remote_hostname_ = remote_entries[i].second;
}
if (selected) {
ImGui::SetItemDefaultFocus();
@@ -351,7 +362,7 @@ int Render::RemoteClientInfoWindow() {
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_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);
if (ImGui::Button(ICON_FA_XMARK, ImVec2(close_connection_button_width,
close_connection_button_height))) {

View File

@@ -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) {
// std::unique_lock lock(client_properties_mutex_);
if (it != client_properties_.end()) {
@@ -117,7 +145,9 @@ int Render::StreamWindow() {
ImGui::SetWindowFontScale(0.6f);
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_, stream_window_height_),
ImVec2(stream_window_width_,
stream_window_height_ -
(fullscreen_button_pressed_ ? 0 : title_bar_height_)),
ImGuiCond_Always);
ImGui::SetNextWindowPos(
ImVec2(0, fullscreen_button_pressed_ ? 0 : title_bar_height_),
@@ -138,10 +168,12 @@ int Render::StreamWindow() {
UpdateRenderRect();
ControlWindow(props);
// Show file transfer window if needed
FileTransferWindow(props);
DrawReceivingScreenText(props);
focused_remote_id_ = props->remote_id_;
if (!props->peer_) {
@@ -151,12 +183,12 @@ int Render::StreamWindow() {
// std::unique_lock unique_lock(client_properties_mutex_);
auto erase_it = client_properties_.find(remote_id_to_erase);
if (erase_it != client_properties_.end()) {
erase_it = client_properties_.erase(erase_it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
// Ensure we flush pending STREAM_REFRESH_EVENT events and
// clean up peer resources before erasing the entry, otherwise
// SDL events may still hold raw pointers to freed
// SubStreamWindowProperties (including video_frame_mutex_),
// leading to std::system_error when locking.
CloseTab(erase_it);
}
}
// lock.lock();
@@ -217,7 +249,9 @@ int Render::StreamWindow() {
if (props->tab_selected_) {
ImGui::SetNextWindowSize(
ImVec2(stream_window_width_, stream_window_height_),
ImVec2(stream_window_width_,
stream_window_height_ -
(fullscreen_button_pressed_ ? 0 : title_bar_height_)),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0.0f, 0.0f));
@@ -236,10 +270,12 @@ int Render::StreamWindow() {
UpdateRenderRect();
ControlWindow(props);
// Show file transfer window if needed
FileTransferWindow(props);
DrawReceivingScreenText(props);
ImGui::End();
if (!props->peer_) {
@@ -251,12 +287,7 @@ int Render::StreamWindow() {
// std::unique_lock unique_lock(client_properties_mutex_);
auto erase_it = client_properties_.find(remote_id_to_erase);
if (erase_it != client_properties_.end()) {
client_properties_.erase(erase_it);
if (client_properties_.empty()) {
SDL_Event event;
event.type = SDL_EVENT_QUIT;
SDL_PushEvent(&event);
}
CloseTab(erase_it);
}
}
// lock.lock();

View File

@@ -77,8 +77,8 @@ int Render::UpdateNotificationWindow() {
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 5.0f);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, window_rounding_);
ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, window_rounding_ * 0.5f);
ImGui::Begin(
localization::notification[localization_language_index_].c_str(),
nullptr,

View File

@@ -138,6 +138,10 @@ int ScreenCapturerX11::SwitchTo(int monitor_index) {
return 0;
}
int ScreenCapturerX11::ResetToInitialMonitor() {
monitor_index_ = initial_monitor_index_;
return 0;
}
std::vector<DisplayInfo> ScreenCapturerX11::GetDisplayInfoList() {
return display_info_list_;
}

View File

@@ -42,6 +42,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
int Resume(int monitor_index) override;
int SwitchTo(int monitor_index) override;
int ResetToInitialMonitor() override;
std::vector<DisplayInfo> GetDisplayInfoList() override;
@@ -62,6 +63,7 @@ class ScreenCapturerX11 : public ScreenCapturer {
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};
int fps_ = 60;
cb_desktop_data callback_;

View File

@@ -62,6 +62,13 @@ int ScreenCapturerSck::SwitchTo(int monitor_index) {
return -1;
}
int ScreenCapturerSck::ResetToInitialMonitor() {
if (screen_capturer_sck_impl_) {
return screen_capturer_sck_impl_->ResetToInitialMonitor();
}
return -1;
}
std::vector<DisplayInfo> ScreenCapturerSck::GetDisplayInfoList() {
if (screen_capturer_sck_impl_) {
return screen_capturer_sck_impl_->GetDisplayInfoList();

View File

@@ -33,6 +33,7 @@ class ScreenCapturerSck : public ScreenCapturer {
int Resume(int monitor_index) override;
int SwitchTo(int monitor_index) override;
int ResetToInitialMonitor() override;
std::vector<DisplayInfo> GetDisplayInfoList() override;

View File

@@ -70,6 +70,7 @@ class API_AVAILABLE(macos(14.0)) ScreenCapturerSckImpl : public ScreenCapturer {
int Resume(int monitor_index) override { return 0; }
std::vector<DisplayInfo> GetDisplayInfoList() override { return display_info_list_; }
int ResetToInitialMonitor() override;
private:
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
// support full-desktop capture, and will fall back to the first display.
CGDirectDisplayID current_display_ = 0;
int initial_monitor_index_ = 0;
};
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;
}
initial_monitor_index_ = 0;
return 0;
}
@@ -295,6 +298,25 @@ int ScreenCapturerSckImpl::SwitchTo(int monitor_index) {
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() {
std::lock_guard<std::mutex> lock(lock_);
if (stream_) {

View File

@@ -31,6 +31,7 @@ class ScreenCapturer {
virtual std::vector<DisplayInfo> GetDisplayInfoList() = 0;
virtual int SwitchTo(int monitor_index) = 0;
virtual int ResetToInitialMonitor() = 0;
};
} // namespace crossdesk
#endif

View File

@@ -8,7 +8,7 @@
#define _SCREEN_CAPTURER_FACTORY_H_
#ifdef _WIN32
#include "screen_capturer_wgc.h"
#include "screen_capturer_win.h"
#elif __linux__
#include "screen_capturer_x11.h"
#elif __APPLE__
@@ -25,7 +25,7 @@ class ScreenCapturerFactory {
public:
ScreenCapturer* Create() {
#ifdef _WIN32
return new ScreenCapturerWgc();
return new ScreenCapturerWin();
#elif __linux__
return new ScreenCapturerX11();
#elif __APPLE__
@@ -37,4 +37,4 @@ class ScreenCapturerFactory {
}
};
} // namespace crossdesk
#endif
#endif

View 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

View 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

View 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

View 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

View File

@@ -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;
}
@@ -149,6 +147,7 @@ int ScreenCapturerWgc::Init(const int fps, cb_desktop_data cb) {
LOG_INFO("Default on monitor {}:{}", monitor_index_,
display_info_list_[monitor_index_].name);
initial_monitor_index_ = monitor_index_;
return 0;
}
@@ -165,6 +164,8 @@ int ScreenCapturerWgc::Start(bool show_cursor) {
return 4;
}
bool any_started = false;
int last_error = 0;
for (int i = 0; i < sessions_.size(); i++) {
if (sessions_[i].inited_ == false) {
LOG_ERROR("Session {} not inited", i);
@@ -174,17 +175,27 @@ int ScreenCapturerWgc::Start(bool show_cursor) {
if (sessions_[i].running_) {
LOG_ERROR("Session {} is already running", i);
} 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) {
sessions_[i].session_->Pause();
sessions_[i].paused_ = 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;
}
@@ -257,6 +268,26 @@ int ScreenCapturerWgc::SwitchTo(int monitor_index) {
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,
int id) {
if (!running_ || !on_data_) {

View File

@@ -34,6 +34,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
std::vector<DisplayInfo> GetDisplayInfoList() { return display_info_list_; }
int SwitchTo(int monitor_index);
int ResetToInitialMonitor() override;
void OnFrame(const WgcSession::wgc_session_frame& frame, int id);
@@ -45,6 +46,7 @@ class ScreenCapturerWgc : public ScreenCapturer,
MONITORINFOEX monitor_info_;
std::vector<DisplayInfo> display_info_list_;
int monitor_index_ = 0;
int initial_monitor_index_ = 0;
private:
class WgcSessionInfo {
@@ -57,8 +59,8 @@ class ScreenCapturerWgc : public ScreenCapturer,
std::vector<WgcSessionInfo> sessions_;
std::atomic_bool running_;
std::atomic_bool inited_;
std::atomic_bool running_{false};
std::atomic_bool inited_{false};
int fps_ = 60;

View 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

View 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

View File

@@ -7,6 +7,8 @@
#include <iostream>
#include <memory>
#include "rd_log.h"
#define CHECK_INIT \
if (!is_initialized_) { \
std::cout << "AE_NEED_INIT" << std::endl; \
@@ -64,6 +66,7 @@ int WgcSessionImpl::Start(bool show_cursor) {
CHECK_INIT;
try {
last_show_cursor_ = show_cursor;
if (!capture_session_) {
auto current_size = capture_item_.Size();
capture_framepool_ =
@@ -89,13 +92,12 @@ int WgcSessionImpl::Start(bool show_cursor) {
// we need to test the performance later
// loop_ = std::thread(std::bind(&WgcSessionImpl::message_func, this));
capture_session_.StartCapture();
capture_session_.IsCursorCaptureEnabled(show_cursor);
capture_session_.StartCapture();
error = 0;
} catch (winrt::hresult_error) {
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
return 86;
} catch (...) {
return 86;
@@ -246,8 +248,15 @@ void WgcSessionImpl::OnFrame(
auto frame_captured =
GetDXGIInterfaceFromObject<ID3D11Texture2D>(frame.Surface());
if (!d3d11_texture_mapped_ || is_new_size)
CreateMappedTexture(frame_captured);
if (!d3d11_texture_mapped_ || is_new_size) {
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(),
frame_captured.get());
@@ -262,6 +271,7 @@ void WgcSessionImpl::OnFrame(
if (FAILED(hr)) {
OutputDebugStringW(
(L"map resource failed: " + std::to_wstring(hr)).c_str());
return;
}
// copy data from map_result.pData
@@ -290,7 +300,24 @@ void WgcSessionImpl::OnFrame(
void WgcSessionImpl::OnClosed(
winrt::Windows::Graphics::Capture::GraphicsCaptureItem 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() {
@@ -313,7 +340,7 @@ int WgcSessionImpl::Initialize() {
d3d11_device->GetImmediateContext(d3d11_device_context_.put());
} catch (winrt::hresult_error) {
std::cout << "AE_WGC_CREATE_CAPTURER_FAILED" << std::endl;
LOG_ERROR("AE_WGC_CREATE_CAPTURER_FAILED");
return 86;
} catch (...) {
return 86;
@@ -378,4 +405,4 @@ LRESULT CALLBACK WindowProc(HWND window, UINT message, WPARAM w_param,
// ::CloseWindow(hwnd_);
// ::DestroyWindow(hwnd_);
// }
} // namespace crossdesk
} // namespace crossdesk

View File

@@ -79,6 +79,7 @@ class WgcSessionImpl : public WgcSession {
bool is_initialized_ = false;
bool is_running_ = false;
bool is_paused_ = false;
bool last_show_cursor_ = false;
wgc_session_observer* observer_ = nullptr;
@@ -116,4 +117,4 @@ class WgcSessionImpl : public WgcSession {
// return result;
// }
} // namespace crossdesk
#endif
#endif

View File

@@ -39,7 +39,7 @@ if is_os("windows") then
add_requires("libyuv", "miniaudio 0.11.21")
add_links("Shell32", "windowsapp", "dwmapi", "User32", "kernel32",
"SDL3-static", "gdi32", "winmm", "setupapi", "version",
"Imm32", "iphlpapi")
"Imm32", "iphlpapi", "d3d11", "dxgi")
add_cxflags("/WX")
set_runtimes("MT")
elseif is_os("linux") then