Compare commits

..

8 Commits

Author SHA1 Message Date
dijunkun
9b69596af1 [fix] fix stream window size recalculation 2026-01-20 01:33:27 +08:00
dijunkun
f6e169c013 [feat] add support for server window resizing and dragging 2026-01-20 01:22:14 +08:00
dijunkun
fd242d50c1 [feat] show server window in the bottom-right corner of the screen 2026-01-19 17:42:22 +08:00
dijunkun
d6d8ecd6c5 [feat] add server window 2026-01-19 00:47:34 +08:00
dijunkun
669fac7f50 [feat] support drag-and-drop file sending, refs #63 2026-01-14 18:13:22 +08:00
dijunkun
92d670916e [fix] fix incorrect data send function used for control data 2026-01-13 18:13:54 +08:00
dijunkun
0155413c12 [feat] use reliable transmission to send control info 2026-01-12 17:28:19 +08:00
dijunkun
8468be6532 [fix] update MiniRTC 2026-01-12 17:27:49 +08:00
8 changed files with 585 additions and 114 deletions

View File

@@ -207,6 +207,7 @@ int Render::ConnectTo(const std::string& remote_id, const char* password,
}
AddAudioStream(props->peer_, props->audio_label_.c_str());
AddDataStream(props->peer_, props->data_label_.c_str(), false);
AddDataStream(props->peer_, props->control_data_label_.c_str(), true);
AddDataStream(props->peer_, props->file_label_.c_str(), true);
AddDataStream(props->peer_, props->file_feedback_label_.c_str(), true);
AddDataStream(props->peer_, props->clipboard_label_.c_str(), true);

View File

@@ -716,6 +716,7 @@ int Render::CreateConnectionPeer() {
AddAudioStream(peer_, audio_label_.c_str());
AddDataStream(peer_, data_label_.c_str(), false);
AddDataStream(peer_, control_data_label_.c_str(), true);
AddDataStream(peer_, file_label_.c_str(), true);
AddDataStream(peer_, file_feedback_label_.c_str(), true);
AddDataStream(peer_, clipboard_label_.c_str(), true);
@@ -810,10 +811,10 @@ int Render::CreateMainWindow() {
if (std::abs(dpi_scale_ - dpi_scale) > 0.01f) {
dpi_scale_ = dpi_scale;
main_window_width_ = (int)(main_window_width_ * dpi_scale_);
main_window_height_ = (int)(main_window_height_ * dpi_scale_);
stream_window_width_ = (int)(stream_window_width_ * dpi_scale_);
stream_window_height_ = (int)(stream_window_height_ * dpi_scale_);
main_window_width_ = (int)(main_window_width_default_ * dpi_scale_);
main_window_height_ = (int)(main_window_height_default_ * dpi_scale_);
stream_window_width_ = (int)(stream_window_width_default_ * dpi_scale_);
stream_window_height_ = (int)(stream_window_height_default_ * dpi_scale_);
SDL_SetWindowSize(main_window_, (int)main_window_width_,
(int)main_window_height_);
@@ -824,7 +825,7 @@ int Render::CreateMainWindow() {
// for window region action
SDL_SetWindowHitTest(main_window_, HitTestCallback, this);
SetupFontAndStyle(true);
SetupFontAndStyle(&main_windows_system_chinese_font_);
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(dpi_scale_);
@@ -868,6 +869,9 @@ int Render::CreateStreamWindow() {
return 0;
}
stream_window_width_ = (int)(stream_window_width_default_ * dpi_scale_);
stream_window_height_ = (int)(stream_window_height_default_ * dpi_scale_);
stream_ctx_ = ImGui::CreateContext();
if (!stream_ctx_) {
LOG_ERROR("Stream context is null");
@@ -893,7 +897,7 @@ int Render::CreateStreamWindow() {
// for window region action
SDL_SetWindowHitTest(stream_window_, HitTestCallback, this);
SetupFontAndStyle(false);
SetupFontAndStyle(&stream_windows_system_chinese_font_);
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(dpi_scale_);
@@ -938,7 +942,81 @@ int Render::DestroyStreamWindow() {
return 0;
}
int Render::SetupFontAndStyle(bool main_window) {
int Render::CreateServerWindow() {
if (server_window_created_) {
return 0;
}
server_ctx_ = ImGui::CreateContext();
if (!server_ctx_) {
LOG_ERROR("Server context is null");
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_)) {
LOG_ERROR("Error creating server_window_ and server_renderer_: {}",
SDL_GetError());
return -1;
}
// Set window position to bottom-right corner
SDL_Rect display_bounds;
if (SDL_GetDisplayUsableBounds(SDL_GetDisplayForWindow(server_window_),
&display_bounds)) {
int window_x =
display_bounds.x + display_bounds.w - (int)server_window_width_;
int window_y =
display_bounds.y + display_bounds.h - (int)server_window_height_;
SDL_SetWindowPosition(server_window_, window_x, window_y);
}
SDL_SetWindowResizable(server_window_, false);
SDL_SetRenderDrawBlendMode(server_renderer_, SDL_BLENDMODE_BLEND);
// for window region action
SDL_SetWindowHitTest(server_window_, HitTestCallback, this);
SetupFontAndStyle(&server_windows_system_chinese_font_);
ImGuiStyle& style = ImGui::GetStyle();
style.ScaleAllSizes(dpi_scale_);
style.FontScaleDpi = dpi_scale_;
ImGui_ImplSDL3_InitForSDLRenderer(server_window_, server_renderer_);
ImGui_ImplSDLRenderer3_Init(server_renderer_);
server_window_created_ = true;
server_window_inited_ = true;
LOG_INFO("Server window inited");
return 0;
}
int Render::DestroyServerWindow() {
if (server_ctx_) {
ImGui::SetCurrentContext(server_ctx_);
}
if (server_renderer_) {
SDL_DestroyRenderer(server_renderer_);
}
if (server_window_) {
SDL_DestroyWindow(server_window_);
}
server_window_created_ = false;
return 0;
}
int Render::SetupFontAndStyle(ImFont** system_chinese_font_out) {
float font_size = 32.0f;
// Setup Dear ImGui style
@@ -960,10 +1038,8 @@ int Render::SetupFontAndStyle(bool main_window) {
// Load system Chinese font as fallback
config.MergeMode = false;
config.FontDataOwnedByAtlas = false;
if (main_window) {
main_windows_system_chinese_font_ = nullptr;
} else {
stream_windows_system_chinese_font_ = nullptr;
if (system_chinese_font_out) {
*system_chinese_font_out = nullptr;
}
#if defined(_WIN32)
@@ -990,37 +1066,36 @@ int Render::SetupFontAndStyle(bool main_window) {
std::ifstream font_file(font_paths[i], std::ios::binary);
if (font_file.good()) {
font_file.close();
if (main_window) {
main_windows_system_chinese_font_ =
io.Fonts->AddFontFromFileTTF(font_paths[i], font_size, &config,
io.Fonts->GetGlyphRangesChineseFull());
if (main_windows_system_chinese_font_ != nullptr) {
LOG_INFO("Loaded system Chinese font: {}", font_paths[i]);
break;
}
} else {
stream_windows_system_chinese_font_ =
io.Fonts->AddFontFromFileTTF(font_paths[i], font_size, &config,
io.Fonts->GetGlyphRangesChineseFull());
if (stream_windows_system_chinese_font_ != nullptr) {
LOG_INFO("Loaded system Chinese font: {}", font_paths[i]);
break;
}
if (!system_chinese_font_out) {
break;
}
*system_chinese_font_out =
io.Fonts->AddFontFromFileTTF(font_paths[i], font_size, &config,
io.Fonts->GetGlyphRangesChineseFull());
if (*system_chinese_font_out != nullptr) {
// Merge FontAwesome icons into the Chinese font
config.MergeMode = true;
static const ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
io.Fonts->AddFontFromMemoryTTF(fa_solid_900_ttf, fa_solid_900_ttf_len,
font_size, &config, icon_ranges);
config.MergeMode = false;
LOG_INFO("Loaded system Chinese font with icons: {}", font_paths[i]);
break;
}
}
}
// If no system font found, use default font
if (main_window) {
if (main_windows_system_chinese_font_ == nullptr) {
main_windows_system_chinese_font_ = io.Fonts->AddFontDefault(&config);
LOG_WARN("System Chinese font not found, using default font");
}
} else {
if (stream_windows_system_chinese_font_ == nullptr) {
stream_windows_system_chinese_font_ = io.Fonts->AddFontDefault(&config);
LOG_WARN("System Chinese font not found, using default font");
}
if (system_chinese_font_out && *system_chinese_font_out == nullptr) {
*system_chinese_font_out = io.Fonts->AddFontDefault(&config);
// Merge FontAwesome icons into the default font
config.MergeMode = true;
static const ImWchar icon_ranges[] = {ICON_MIN_FA, ICON_MAX_FA, 0};
io.Fonts->AddFontFromMemoryTTF(fa_solid_900_ttf, fa_solid_900_ttf_len,
font_size, &config, icon_ranges);
config.MergeMode = false;
LOG_WARN("System Chinese font not found, using default font with icons");
}
ImGui::StyleColorsLight();
@@ -1158,6 +1233,35 @@ int Render::DrawStreamWindow() {
return 0;
}
int Render::DrawServerWindow() {
if (!server_ctx_) {
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();
ServerWindow();
ImGui::Render();
SDL_SetRenderDrawColor(server_renderer_, 255, 255, 255, 255);
SDL_RenderClear(server_renderer_);
ImGui_ImplSDLRenderer3_RenderDrawData(ImGui::GetDrawData(), server_renderer_);
SDL_RenderPresent(server_renderer_);
return 0;
}
int Render::Run() {
latest_version_info_ = CheckUpdate();
if (!latest_version_info_.empty() &&
@@ -1332,12 +1436,17 @@ void Render::MainLoop() {
UpdateLabels();
HandleRecentConnections();
HandleStreamWindow();
HandleServerWindow();
DrawMainWindow();
if (stream_window_inited_) {
DrawStreamWindow();
}
if (is_server_mode_) {
DrawServerWindow();
}
UpdateInteractions();
if (need_to_send_host_info_) {
@@ -1375,8 +1484,8 @@ void Render::MainLoop() {
remote_action.i.host_name_size = host_name.size();
std::string msg = remote_action.to_json();
int ret =
SendDataFrame(peer_, msg.data(), msg.size(), data_label_.c_str());
int ret = SendReliableDataFrame(peer_, msg.data(), msg.size(),
control_data_label_.c_str());
FreeRemoteAction(remote_action);
if (0 == ret) {
need_to_send_host_info_ = false;
@@ -1429,6 +1538,18 @@ void Render::HandleStreamWindow() {
}
}
void Render::HandleServerWindow() {
if (need_to_create_server_window_) {
CreateServerWindow();
need_to_create_server_window_ = false;
}
if (need_to_destroy_server_window_) {
DestroyServerWindow();
need_to_destroy_server_window_ = false;
}
}
void Render::Cleanup() {
Clipboard::StopMonitoring();
@@ -1807,6 +1928,16 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
}
}
if (server_window_inited_) {
if (server_ctx_) {
ImGui::SetCurrentContext(server_ctx_);
ImGui_ImplSDL3_ProcessEvent(&event);
} else {
LOG_ERROR("Server context is null");
return;
}
}
switch (event.type) {
case SDL_EVENT_QUIT:
if (stream_window_inited_) {
@@ -1896,6 +2027,12 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
foucs_on_main_window_ = false;
}
break;
case SDL_EVENT_DROP_FILE:
if (stream_window_ &&
SDL_GetWindowID(stream_window_) == event.window.windowID) {
ProcessFileDropEvent(event);
}
break;
case SDL_EVENT_MOUSE_MOTION:
case SDL_EVENT_MOUSE_BUTTON_DOWN:
@@ -1976,4 +2113,63 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
break;
}
}
void Render::ProcessFileDropEvent(const SDL_Event& event) {
if (event.type != SDL_EVENT_DROP_FILE) {
return;
}
if (!stream_window_inited_) {
return;
}
std::shared_lock lock(client_properties_mutex_);
for (auto& [_, props] : client_properties_) {
if (props->tab_selected_) {
if (event.drop.data == nullptr) {
LOG_ERROR("ProcessFileDropEvent: drop event data is null");
break;
}
if (!props || !props->peer_) {
LOG_ERROR("ProcessFileDropEvent: invalid props or peer");
break;
}
std::string utf8_path = static_cast<const char*>(event.drop.data);
std::filesystem::path file_path = std::filesystem::u8path(utf8_path);
// Check if file exists
std::error_code ec;
if (!std::filesystem::exists(file_path, ec)) {
LOG_ERROR("ProcessFileDropEvent: file does not exist: {}",
file_path.string().c_str());
break;
}
// Check if it's a regular file
if (!std::filesystem::is_regular_file(file_path, ec)) {
LOG_ERROR("ProcessFileDropEvent: path is not a regular file: {}",
file_path.string().c_str());
break;
}
// Get file size
uint64_t file_size = std::filesystem::file_size(file_path, ec);
if (ec) {
LOG_ERROR("ProcessFileDropEvent: failed to get file size: {}",
ec.message().c_str());
break;
}
LOG_INFO("Drop file [{}] to send (size: {} bytes)", event.drop.data,
file_size);
// Use ProcessSelectedFile to handle the file processing
ProcessSelectedFile(utf8_path, props, props->file_label_);
break;
}
}
}
} // namespace crossdesk

View File

@@ -46,8 +46,9 @@ class Render {
Params params_;
PeerPtr* peer_ = nullptr;
std::string audio_label_ = "control_audio";
std::string data_label_ = "control_data";
std::string data_label_ = "data";
std::string file_label_ = "file";
std::string control_data_label_ = "control_data";
std::string file_feedback_label_ = "file_feedback";
std::string clipboard_label_ = "clipboard";
std::string local_id_ = "";
@@ -180,6 +181,7 @@ class Render {
void UpdateInteractions();
void HandleRecentConnections();
void HandleStreamWindow();
void HandleServerWindow();
void Cleanup();
void CleanupFactories();
void CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props);
@@ -189,12 +191,19 @@ class Render {
void UpdateRenderRect();
void ProcessSdlEvent(const SDL_Event& event);
void ProcessFileDropEvent(const SDL_Event& event);
void ProcessSelectedFile(const std::string& path,
std::shared_ptr<SubStreamWindowProperties>& props,
const std::string& file_label);
private:
int CreateStreamRenderWindow();
int TitleBar(bool main_window);
int MainWindow();
int UpdateNotificationWindow();
int StreamWindow();
int ServerWindow();
int LocalWindow();
int RemoteWindow();
int RecentConnectionsWindow();
@@ -219,11 +228,15 @@ class Render {
int DestroyMainWindow();
int CreateStreamWindow();
int DestroyStreamWindow();
int SetupFontAndStyle(bool main_window);
int CreateServerWindow();
int DestroyServerWindow();
int SetupFontAndStyle(ImFont** system_chinese_font_out);
int DestroyMainWindowContext();
int DestroyStreamWindowContext();
int DestroyServerWindowContext();
int DrawMainWindow();
int DrawStreamWindow();
int DrawServerWindow();
int ConfirmDeleteConnection();
int NetTrafficStats(std::shared_ptr<SubStreamWindowProperties>& props);
void DrawConnectionStatusText(
@@ -382,6 +395,7 @@ class Render {
ImGuiContext* main_ctx_ = nullptr;
ImFont* main_windows_system_chinese_font_ = nullptr;
ImFont* stream_windows_system_chinese_font_ = nullptr;
ImFont* server_windows_system_chinese_font_ = nullptr;
bool exit_ = false;
const int sdl_refresh_ms_ = 16; // ~60 FPS
#if _WIN32
@@ -480,6 +494,42 @@ class Render {
float stream_window_dpi_scaling_w_ = 1.0f;
float stream_window_dpi_scaling_h_ = 1.0f;
// server window render
SDL_Window* server_window_ = nullptr;
SDL_Renderer* server_renderer_ = nullptr;
ImGuiContext* server_ctx_ = nullptr;
// server window properties
bool need_to_create_server_window_ = false;
bool need_to_destroy_server_window_ = false;
bool server_window_created_ = false;
bool server_window_inited_ = false;
int server_window_width_default_ = 300;
int server_window_height_default_ = 450;
float server_window_width_ = 300;
float server_window_height_ = 450;
float server_window_title_bar_height_ = 50.0f;
SDL_PixelFormat server_pixformat_ = SDL_PIXELFORMAT_NV12;
int server_window_normal_width_ = 300;
int server_window_normal_height_ = 450;
float server_window_dpi_scaling_w_ = 1.0f;
float server_window_dpi_scaling_h_ = 1.0f;
// server window collapsed mode
bool server_window_collapsed_ = false;
bool server_window_collapsed_dragging_ = false;
float server_window_collapsed_drag_start_mouse_x_ = 0.0f;
float server_window_collapsed_drag_start_mouse_y_ = 0.0f;
int server_window_collapsed_drag_start_win_x_ = 0;
int server_window_collapsed_drag_start_win_y_ = 0;
// server window drag normal mode
bool server_window_dragging_ = false;
float server_window_drag_start_mouse_x_ = 0.0f;
float server_window_drag_start_mouse_y_ = 0.0f;
int server_window_drag_start_win_x_ = 0;
int server_window_drag_start_win_y_ = 0;
bool label_inited_ = false;
bool connect_button_pressed_ = false;
bool password_validating_ = false;
@@ -496,6 +546,7 @@ class Render {
bool fullscreen_button_pressed_ = false;
bool focus_on_input_widget_ = true;
bool is_client_mode_ = false;
bool is_server_mode_ = false;
bool reload_recent_connections_ = true;
bool show_confirm_delete_connection_ = false;
bool delete_connection_ = false;

View File

@@ -568,6 +568,7 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
switch (status) {
case ConnectionStatus::Connected: {
render->need_to_send_host_info_ = true;
if (!render->need_to_create_stream_window_ &&
!render->client_properties_.empty()) {
render->need_to_create_stream_window_ = true;
@@ -622,7 +623,9 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
switch (status) {
case ConnectionStatus::Connected: {
render->need_to_create_server_window_ = true;
render->need_to_send_host_info_ = true;
render->is_server_mode_ = true;
render->start_screen_capturer_ = true;
render->start_speaker_capturer_ = true;
#ifdef CROSSDESK_DEBUG
@@ -647,6 +650,8 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
kv.second == ConnectionStatus::Failed ||
kv.second == ConnectionStatus::Disconnected;
})) {
render->need_to_destroy_server_window_ = true;
render->is_server_mode_ = false;
render->start_screen_capturer_ = false;
render->start_speaker_capturer_ = false;
render->start_mouse_controller_ = false;

View File

@@ -53,6 +53,77 @@ int LossRateDisplay(float loss_rate) {
return 0;
}
void Render::ProcessSelectedFile(
const std::string& path, std::shared_ptr<SubStreamWindowProperties>& props,
const std::string& file_label) {
if (path.empty()) {
return;
}
LOG_INFO("Selected file: {}", path.c_str());
std::filesystem::path file_path = std::filesystem::u8path(path);
// Get file size
std::error_code ec;
uint64_t file_size = std::filesystem::file_size(file_path, ec);
if (ec) {
LOG_ERROR("Failed to get file size: {}", ec.message().c_str());
file_size = 0;
}
// Add file to transfer list
{
std::lock_guard<std::mutex> lock(props->file_transfer_list_mutex_);
SubStreamWindowProperties::FileTransferInfo info;
info.file_name = file_path.filename().u8string();
info.file_path = file_path; // Store full path for precise matching
info.file_size = file_size;
info.status = SubStreamWindowProperties::FileTransferStatus::Queued;
info.sent_bytes = 0;
info.file_id = 0;
info.rate_bps = 0;
props->file_transfer_list_.push_back(info);
}
props->file_transfer_window_visible_ = true;
if (props->file_sending_.load()) {
// Add to queue
size_t queue_size = 0;
{
std::lock_guard<std::mutex> lock(props->file_queue_mutex_);
SubStreamWindowProperties::QueuedFile queued_file;
queued_file.file_path = file_path;
queued_file.file_label = file_label;
props->file_send_queue_.push(queued_file);
queue_size = props->file_send_queue_.size();
}
LOG_INFO("File added to queue: {} ({} files in queue)",
file_path.filename().string().c_str(), queue_size);
} else {
StartFileTransfer(props, file_path, file_label);
if (props->file_sending_.load()) {
} else {
// Failed to start (race condition: another file started between
// check and call) Add to queue
size_t queue_size = 0;
{
std::lock_guard<std::mutex> lock(props->file_queue_mutex_);
SubStreamWindowProperties::QueuedFile queued_file;
queued_file.file_path = file_path;
queued_file.file_label = file_label;
props->file_send_queue_.push(queued_file);
queue_size = props->file_send_queue_.size();
}
LOG_INFO(
"File added to queue after race condition: {} ({} files in "
"queue)",
file_path.filename().string().c_str(), queue_size);
}
}
}
int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
float button_width = title_bar_height_ * 0.8f;
float button_height = title_bar_height_ * 0.8f;
@@ -95,8 +166,8 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
remote_action.d = i;
if (props->connection_status_ == ConnectionStatus::Connected) {
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
SendReliableDataFrame(props->peer_, msg.c_str(), msg.size(),
props->control_data_label_.c_str());
}
}
props->display_selectable_hovered_ = ImGui::IsWindowHovered();
@@ -176,8 +247,8 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
remote_action.type = ControlType::audio_capture;
remote_action.a = props->audio_capture_button_pressed_;
std::string msg = remote_action.to_json();
SendDataFrame(props->peer_, msg.c_str(), msg.size(),
props->data_label_.c_str());
SendReliableDataFrame(props->peer_, msg.c_str(), msg.size(),
props->control_data_label_.c_str());
}
}
@@ -204,71 +275,7 @@ int Render::ControlBar(std::shared_ptr<SubStreamWindowProperties>& props) {
std::string title =
localization::select_file[localization_language_index_];
std::string path = OpenFileDialog(title);
if (!path.empty()) {
LOG_INFO("Selected file: {}", path.c_str());
std::filesystem::path file_path = std::filesystem::path(path);
std::string file_label = file_label_;
// Get file size
std::error_code ec;
uint64_t file_size = std::filesystem::file_size(file_path, ec);
if (ec) {
LOG_ERROR("Failed to get file size: {}", ec.message().c_str());
file_size = 0;
}
// Add file to transfer list
{
std::lock_guard<std::mutex> lock(props->file_transfer_list_mutex_);
SubStreamWindowProperties::FileTransferInfo info;
info.file_name = file_path.filename().string();
info.file_path = file_path; // Store full path for precise matching
info.file_size = file_size;
info.status = SubStreamWindowProperties::FileTransferStatus::Queued;
info.sent_bytes = 0;
info.file_id = 0;
info.rate_bps = 0;
props->file_transfer_list_.push_back(info);
}
props->file_transfer_window_visible_ = true;
if (props->file_sending_.load()) {
// Add to queue
size_t queue_size = 0;
{
std::lock_guard<std::mutex> lock(props->file_queue_mutex_);
SubStreamWindowProperties::QueuedFile queued_file;
queued_file.file_path = file_path;
queued_file.file_label = file_label;
props->file_send_queue_.push(queued_file);
queue_size = props->file_send_queue_.size();
}
LOG_INFO("File added to queue: {} ({} files in queue)",
file_path.filename().string().c_str(), queue_size);
} else {
StartFileTransfer(props, file_path, file_label);
if (props->file_sending_.load()) {
} else {
// Failed to start (race condition: another file started between
// check and call) Add to queue
size_t queue_size = 0;
{
std::lock_guard<std::mutex> lock(props->file_queue_mutex_);
SubStreamWindowProperties::QueuedFile queued_file;
queued_file.file_path = file_path;
queued_file.file_label = file_label;
props->file_send_queue_.push(queued_file);
queue_size = props->file_send_queue_.size();
}
LOG_INFO(
"File added to queue after race condition: {} ({} files in "
"queue)",
file_path.filename().string().c_str(), queue_size);
}
}
}
this->ProcessSelectedFile(path, props, file_label_);
}
ImGui::SameLine();

View File

@@ -87,6 +87,11 @@ int Render::FileTransferWindow(
ImVec2(file_transfer_window_width, file_transfer_window_height),
ImGuiCond_Always);
// Set Chinese font for proper display
if (stream_windows_system_chinese_font_) {
ImGui::PushFont(stream_windows_system_chinese_font_);
}
ImGui::PushStyleVar(ImGuiStyleVar_WindowRounding, 3.0f);
ImGui::PushStyleVar(ImGuiStyleVar_WindowBorderSize, 1.0f);
ImGui::PushStyleColor(ImGuiCol_WindowBg, ImVec4(1.0f, 1.0f, 1.0f, 0.9f));
@@ -121,9 +126,9 @@ int Render::FileTransferWindow(
} else {
// Use a scrollable child window for the file list
ImGui::SetWindowFontScale(0.5f);
ImGui::BeginChild("FileList",
ImVec2(0, file_transfer_window_height * 0.75f),
ImGuiChildFlags_Border);
ImGui::BeginChild(
"FileList", ImVec2(0, file_transfer_window_height * 0.75f),
ImGuiChildFlags_Border, ImGuiWindowFlags_HorizontalScrollbar);
ImGui::SetWindowFontScale(1.0f);
ImGui::SetWindowFontScale(0.5f);
@@ -220,6 +225,11 @@ int Render::FileTransferWindow(
ImGui::SetWindowFontScale(0.5f);
ImGui::End();
ImGui::SetWindowFontScale(1.0f);
// Pop Chinese font if it was pushed
if (stream_windows_system_chinese_font_) {
ImGui::PopFont();
}
} else {
ImGui::PopStyleColor(4);
ImGui::PopStyleVar(2);

View File

@@ -0,0 +1,201 @@
#include "rd_log.h"
#include "render.h"
namespace crossdesk {
namespace {
constexpr float kDragThresholdPx = 3.0f;
// Handles dragging for the *last submitted ImGui item*.
// `reset_on_deactivate` should be false when the caller needs to know whether a
// deactivation was a click (no drag) vs a drag-release (dragging==true).
inline void HandleWindowDragForLastItem(SDL_Window* window, bool& dragging,
float& start_mouse_x,
float& start_mouse_y, int& start_win_x,
int& start_win_y,
bool reset_on_deactivate = true) {
if (!window) {
return;
}
if (ImGui::IsItemActivated()) {
SDL_GetGlobalMouseState(&start_mouse_x, &start_mouse_y);
SDL_GetWindowPosition(window, &start_win_x, &start_win_y);
dragging = false;
}
if (ImGui::IsItemActive()) {
if (!dragging &&
ImGui::IsMouseDragging(ImGuiMouseButton_Left, kDragThresholdPx)) {
dragging = true;
}
if (dragging) {
float mouse_x = 0.0f;
float mouse_y = 0.0f;
SDL_GetGlobalMouseState(&mouse_x, &mouse_y);
const int dx = (int)(mouse_x - start_mouse_x);
const int dy = (int)(mouse_y - start_mouse_y);
SDL_SetWindowPosition(window, start_win_x + dx, start_win_y + dy);
}
}
if (reset_on_deactivate && ImGui::IsItemDeactivated()) {
dragging = false;
}
}
} // namespace
int Render::ServerWindow() {
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
ImGuiCond_Always);
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::Begin("##server_window", nullptr,
ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoResize |
ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoCollapse |
ImGuiWindowFlags_NoScrollbar |
ImGuiWindowFlags_NoScrollWithMouse);
// Collapsed mode: no buttons; drag to move; click to restore.
if (server_window_collapsed_) {
ImGui::SetCursorPos(ImVec2(0.0f, 0.0f));
ImGui::InvisibleButton("##server_collapsed_area",
ImVec2(server_window_width_, server_window_height_));
HandleWindowDragForLastItem(server_window_,
server_window_collapsed_dragging_,
server_window_collapsed_drag_start_mouse_x_,
server_window_collapsed_drag_start_mouse_y_,
server_window_collapsed_drag_start_win_x_,
server_window_collapsed_drag_start_win_y_,
/*reset_on_deactivate=*/false);
const bool request_restore =
ImGui::IsItemDeactivated() && !server_window_collapsed_dragging_;
if (ImGui::IsItemDeactivated()) {
server_window_collapsed_dragging_ = false;
}
if (request_restore && server_window_) {
int w = 0;
int x = 0;
int y = 0;
int h = 0;
SDL_GetWindowSize(server_window_, &w, &h);
SDL_GetWindowPosition(server_window_, &x, &y);
const int normal_h = server_window_normal_height_;
SDL_SetWindowSize(server_window_, w, normal_h);
SDL_SetWindowPosition(server_window_, x, y);
server_window_collapsed_ = false;
}
ImGui::End();
return 0;
}
ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
ImGui::SetNextWindowPos(ImVec2(0, 0), ImGuiCond_Always);
ImGui::BeginChild(
"ServerTitleBar",
ImVec2(server_window_width_, server_window_title_bar_height_),
ImGuiChildFlags_Border,
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoMove |
ImGuiWindowFlags_NoBringToFrontOnFocus);
ImDrawList* draw_list = ImGui::GetWindowDrawList();
float server_title_bar_button_width = server_window_title_bar_height_;
float server_title_bar_button_height = server_window_title_bar_height_;
// Drag area: the title bar excluding the right-side buttons.
{
const float drag_w =
server_window_width_ - server_title_bar_button_width * 2;
const float drag_h = server_title_bar_button_height;
ImGui::SetCursorPos(ImVec2(0.0f, 0.0f));
ImGui::InvisibleButton("##server_title_drag_area", ImVec2(drag_w, drag_h));
HandleWindowDragForLastItem(
server_window_, server_window_dragging_,
server_window_drag_start_mouse_x_, server_window_drag_start_mouse_y_,
server_window_drag_start_win_x_, server_window_drag_start_win_y_);
}
float minimize_button_pos_x =
server_window_width_ - server_title_bar_button_width * 2;
ImGui::SetCursorPos(ImVec2(minimize_button_pos_x, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(0, 0, 0, 0.1f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 1.0f, 1.0f, 1.0f));
float minimize_pos_x =
minimize_button_pos_x + server_title_bar_button_width * 0.33f;
float minimize_pos_y = server_title_bar_button_height * 0.5f;
std::string server_minimize_button = "##minimize"; // ICON_FA_MINUS;
if (ImGui::Button(server_minimize_button.c_str(),
ImVec2(server_title_bar_button_width,
server_title_bar_button_height))) {
if (server_window_) {
int w = 0;
int h = 0;
int x = 0;
int y = 0;
SDL_GetWindowSize(server_window_, &w, &h);
SDL_GetWindowPosition(server_window_, &x, &y);
const int collapsed_h = (int)server_window_title_bar_height_;
// Collapse upward: keep top edge stable.
SDL_SetWindowSize(server_window_, w, collapsed_h);
SDL_SetWindowPosition(server_window_, x, y);
server_window_collapsed_ = true;
}
}
draw_list->AddLine(
ImVec2(minimize_pos_x, minimize_pos_y),
ImVec2(minimize_pos_x + server_title_bar_button_width * 0.33f,
minimize_pos_y),
IM_COL32(0, 0, 0, 255));
ImGui::PopStyleColor(3);
float xmark_button_pos_x =
server_window_width_ - server_title_bar_button_width;
ImGui::SetCursorPos(ImVec2(xmark_button_pos_x, 0.0f));
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(0, 0, 0, 0));
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(1.0f, 0, 0, 1.0f));
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(1.0f, 0, 0, 0.5f));
float xmark_pos_x = xmark_button_pos_x + server_title_bar_button_width * 0.5f;
float xmark_pos_y = server_title_bar_button_height * 0.5f;
float xmark_size = server_title_bar_button_width * 0.33f;
std::string server_close_button = "##xmark"; // ICON_FA_XMARK;
if (ImGui::Button(server_close_button.c_str(),
ImVec2(server_title_bar_button_width,
server_title_bar_button_height))) {
LOG_ERROR("Close button clicked");
LeaveConnection(peer_, self_hosted_id_);
}
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,
xmark_pos_y + xmark_size / 2 - 0.5f),
IM_COL32(0, 0, 0, 255));
draw_list->AddLine(
ImVec2(xmark_pos_x + xmark_size / 2 - 1.75f,
xmark_pos_y - xmark_size / 2 + 0.75f),
ImVec2(xmark_pos_x - xmark_size / 2, xmark_pos_y + xmark_size / 2 - 1.0f),
IM_COL32(0, 0, 0, 255));
ImGui::PopStyleColor(3);
ImGui::EndChild();
ImGui::PopStyleVar();
ImGui::PopStyleColor();
ImGui::End();
return 0;
}
} // namespace crossdesk