From 669fac7f50183ecb0741dbcf9c5bbdac423aa3d2 Mon Sep 17 00:00:00 2001 From: dijunkun Date: Wed, 14 Jan 2026 18:13:22 +0800 Subject: [PATCH] [feat] support drag-and-drop file sending, refs #63 --- src/gui/render.cpp | 93 ++++++++++++++- src/gui/render.h | 6 + src/gui/toolbars/control_bar.cpp | 137 ++++++++++++----------- src/gui/windows/file_transfer_window.cpp | 16 ++- 4 files changed, 179 insertions(+), 73 deletions(-) diff --git a/src/gui/render.cpp b/src/gui/render.cpp index 8cea1dd..1aa6b46 100644 --- a/src/gui/render.cpp +++ b/src/gui/render.cpp @@ -996,7 +996,13 @@ int Render::SetupFontAndStyle(bool main_window) { 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]); + // 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; } } else { @@ -1004,7 +1010,13 @@ int Render::SetupFontAndStyle(bool main_window) { 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]); + // 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; } } @@ -1015,12 +1027,24 @@ int Render::SetupFontAndStyle(bool main_window) { 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"); + // 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"); } } 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"); + // 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"); } } @@ -1900,7 +1924,7 @@ void Render::ProcessSdlEvent(const SDL_Event& event) { case SDL_EVENT_DROP_FILE: if (stream_window_ && SDL_GetWindowID(stream_window_) == event.window.windowID) { - printf("SDL_EVENT_DROP_FILE (%s)\n", event.drop.data); + ProcessFileDropEvent(event); } break; @@ -1983,4 +2007,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(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 \ No newline at end of file diff --git a/src/gui/render.h b/src/gui/render.h index cd01de5..7f3a992 100644 --- a/src/gui/render.h +++ b/src/gui/render.h @@ -190,6 +190,12 @@ 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& props, + const std::string& file_label); + private: int CreateStreamRenderWindow(); int TitleBar(bool main_window); diff --git a/src/gui/toolbars/control_bar.cpp b/src/gui/toolbars/control_bar.cpp index 4a88568..f125e41 100644 --- a/src/gui/toolbars/control_bar.cpp +++ b/src/gui/toolbars/control_bar.cpp @@ -53,6 +53,77 @@ int LossRateDisplay(float loss_rate) { return 0; } +void Render::ProcessSelectedFile( + const std::string& path, std::shared_ptr& 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 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 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 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& props) { float button_width = title_bar_height_ * 0.8f; float button_height = title_bar_height_ * 0.8f; @@ -204,71 +275,7 @@ int Render::ControlBar(std::shared_ptr& 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 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 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 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(); diff --git a/src/gui/windows/file_transfer_window.cpp b/src/gui/windows/file_transfer_window.cpp index 677261f..94ef6bf 100644 --- a/src/gui/windows/file_transfer_window.cpp +++ b/src/gui/windows/file_transfer_window.cpp @@ -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);