mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-03-31 22:35:30 +08:00
[feat] use tooltips to display server-side file transfer status information
This commit is contained in:
@@ -4,6 +4,7 @@
|
|||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <filesystem>
|
#include <filesystem>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
|
#include <limits>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
|
||||||
#include "clipboard.h"
|
#include "clipboard.h"
|
||||||
@@ -416,8 +417,46 @@ void Render::OnReceiveDataBufferCb(const char* data, size_t size,
|
|||||||
rate_bps = state->file_send_rate_bps_.load();
|
rate_bps = state->file_send_rate_bps_.load();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Global transfer: no per-connection bitrate, keep existing value.
|
// Global transfer: no per-connection bitrate available.
|
||||||
rate_bps = state->file_send_rate_bps_.load();
|
// Estimate send rate from ACKed bytes delta over time.
|
||||||
|
const uint32_t current_rate = state->file_send_rate_bps_.load();
|
||||||
|
uint32_t estimated_rate_bps = 0;
|
||||||
|
const auto now = std::chrono::steady_clock::now();
|
||||||
|
|
||||||
|
uint64_t last_bytes = 0;
|
||||||
|
std::chrono::steady_clock::time_point last_time;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(state->file_transfer_mutex_);
|
||||||
|
last_bytes = state->file_send_last_bytes_;
|
||||||
|
last_time = state->file_send_last_update_time_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state->file_sending_.load() && ack.acked_offset >= last_bytes) {
|
||||||
|
const uint64_t delta_bytes = ack.acked_offset - last_bytes;
|
||||||
|
const double delta_seconds =
|
||||||
|
std::chrono::duration<double>(now - last_time).count();
|
||||||
|
|
||||||
|
if (delta_seconds > 0.0 && delta_bytes > 0) {
|
||||||
|
const double bps =
|
||||||
|
(static_cast<double>(delta_bytes) * 8.0) / delta_seconds;
|
||||||
|
if (bps > 0.0) {
|
||||||
|
const double capped =
|
||||||
|
(std::min)(bps, static_cast<double>(
|
||||||
|
(std::numeric_limits<uint32_t>::max)()));
|
||||||
|
estimated_rate_bps = static_cast<uint32_t>(capped);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (estimated_rate_bps > 0 && current_rate > 0) {
|
||||||
|
// 70% old + 30% new for smoother display
|
||||||
|
rate_bps = static_cast<uint32_t>(current_rate * 0.7 +
|
||||||
|
estimated_rate_bps * 0.3);
|
||||||
|
} else if (estimated_rate_bps > 0) {
|
||||||
|
rate_bps = estimated_rate_bps;
|
||||||
|
} else {
|
||||||
|
rate_bps = current_rate;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
state->file_send_rate_bps_ = rate_bps;
|
state->file_send_rate_bps_ = rate_bps;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
#include <cmath>
|
||||||
|
#include <cstdio>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
@@ -9,6 +11,39 @@
|
|||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
int CountDigits(int number) {
|
||||||
|
if (number == 0) return 1;
|
||||||
|
return (int)std::floor(std::log10(std::abs(number))) + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
void BitrateDisplay(uint32_t bitrate) {
|
||||||
|
const int num_of_digits = CountDigits(static_cast<int>(bitrate));
|
||||||
|
if (num_of_digits <= 3) {
|
||||||
|
ImGui::Text("%u bps", bitrate);
|
||||||
|
} else if (num_of_digits > 3 && num_of_digits <= 6) {
|
||||||
|
ImGui::Text("%u kbps", bitrate / 1000);
|
||||||
|
} else {
|
||||||
|
ImGui::Text("%.1f mbps", bitrate / 1000000.0f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string FormatBytes(uint64_t bytes) {
|
||||||
|
char buf[64];
|
||||||
|
if (bytes < 1024ULL) {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%llu B", (unsigned long long)bytes);
|
||||||
|
} else if (bytes < 1024ULL * 1024ULL) {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.2f KB", bytes / 1024.0);
|
||||||
|
} else if (bytes < 1024ULL * 1024ULL * 1024ULL) {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.2f MB", bytes / (1024.0 * 1024.0));
|
||||||
|
} else {
|
||||||
|
std::snprintf(buf, sizeof(buf), "%.2f GB",
|
||||||
|
bytes / (1024.0 * 1024.0 * 1024.0));
|
||||||
|
}
|
||||||
|
return std::string(buf);
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
int Render::ServerWindow() {
|
int Render::ServerWindow() {
|
||||||
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
|
ImGui::SetNextWindowSize(ImVec2(server_window_width_, server_window_height_),
|
||||||
ImGuiCond_Always);
|
ImGuiCond_Always);
|
||||||
@@ -235,6 +270,75 @@ int Render::RemoteClientInfoWindow() {
|
|||||||
ProcessSelectedFile(path, nullptr, file_label_, selected_server_remote_id_);
|
ProcessSelectedFile(path, nullptr, file_label_, selected_server_remote_id_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (file_transfer_.file_transfer_window_visible_) {
|
||||||
|
ImGui::SameLine();
|
||||||
|
const bool is_sending = file_transfer_.file_sending_.load();
|
||||||
|
|
||||||
|
if (is_sending) {
|
||||||
|
// Simple animation: cycle icon every 0.5s while sending.
|
||||||
|
static const char* kFileTransferIcons[] = {ICON_FA_ANGLE_UP,
|
||||||
|
ICON_FA_ANGLES_UP};
|
||||||
|
const int icon_index = static_cast<int>(ImGui::GetTime() / 0.5) %
|
||||||
|
(static_cast<int>(sizeof(kFileTransferIcons) /
|
||||||
|
sizeof(kFileTransferIcons[0])));
|
||||||
|
ImGui::Text("%s", kFileTransferIcons[icon_index]);
|
||||||
|
} else {
|
||||||
|
// Completed.
|
||||||
|
ImGui::Text("%s", ICON_FA_CHECK);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ImGui::IsItemHovered()) {
|
||||||
|
const uint64_t sent_bytes = file_transfer_.file_sent_bytes_.load();
|
||||||
|
const uint64_t total_bytes = file_transfer_.file_total_bytes_.load();
|
||||||
|
const uint32_t rate_bps = file_transfer_.file_send_rate_bps_.load();
|
||||||
|
|
||||||
|
float progress = 0.0f;
|
||||||
|
if (total_bytes > 0) {
|
||||||
|
progress =
|
||||||
|
static_cast<float>(sent_bytes) / static_cast<float>(total_bytes);
|
||||||
|
progress = (std::max)(0.0f, (std::min)(1.0f, progress));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string current_file_name;
|
||||||
|
const uint32_t current_file_id = file_transfer_.current_file_id_.load();
|
||||||
|
if (current_file_id != 0) {
|
||||||
|
std::lock_guard<std::mutex> lock(
|
||||||
|
file_transfer_.file_transfer_list_mutex_);
|
||||||
|
for (const auto& info : file_transfer_.file_transfer_list_) {
|
||||||
|
if (info.file_id == current_file_id) {
|
||||||
|
current_file_name = info.file_name;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ImGui::BeginTooltip();
|
||||||
|
if (server_windows_system_chinese_font_) {
|
||||||
|
ImGui::PushFont(server_windows_system_chinese_font_);
|
||||||
|
}
|
||||||
|
ImGui::SetWindowFontScale(0.5f);
|
||||||
|
if (!current_file_name.empty()) {
|
||||||
|
ImGui::Text("%s", current_file_name.c_str());
|
||||||
|
}
|
||||||
|
if (total_bytes > 0) {
|
||||||
|
const std::string sent_str = FormatBytes(sent_bytes);
|
||||||
|
const std::string total_str = FormatBytes(total_bytes);
|
||||||
|
ImGui::Text("%s / %s", sent_str.c_str(), total_str.c_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
const float text_height = ImGui::GetTextLineHeight();
|
||||||
|
char overlay[32];
|
||||||
|
std::snprintf(overlay, sizeof(overlay), "%.1f%%", progress * 100.0f);
|
||||||
|
ImGui::ProgressBar(progress, ImVec2(180.0f, text_height), overlay);
|
||||||
|
BitrateDisplay(rate_bps);
|
||||||
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
|
if (server_windows_system_chinese_font_) {
|
||||||
|
ImGui::PopFont();
|
||||||
|
}
|
||||||
|
ImGui::EndTooltip();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ImGui::SetWindowFontScale(1.0f);
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
|
|
||||||
ImGui::EndChild();
|
ImGui::EndChild();
|
||||||
|
|||||||
Reference in New Issue
Block a user