mirror of
https://github.com/kunkundi/crossdesk.git
synced 2026-03-30 21:55:31 +08:00
Compare commits
7 Commits
e9fce5b8b8
...
v1.2.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2e8ce6a2f0 | ||
|
|
9927a56b78 | ||
|
|
db3da52f83 | ||
|
|
19a7c6978a | ||
|
|
b5e9ba03a1 | ||
|
|
cb5f8b91ad | ||
|
|
f627f60f1a |
@@ -1737,13 +1737,19 @@ void Render::CleanupFactories() {
|
|||||||
void Render::CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props) {
|
void Render::CleanupPeer(std::shared_ptr<SubStreamWindowProperties> props) {
|
||||||
SDL_FlushEvent(STREAM_REFRESH_EVENT);
|
SDL_FlushEvent(STREAM_REFRESH_EVENT);
|
||||||
|
|
||||||
if (props->dst_buffer_) {
|
std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
|
||||||
size_t buffer_size = props->dst_buffer_capacity_;
|
int video_width = 0;
|
||||||
std::vector<unsigned char> buffer_copy(buffer_size);
|
int video_height = 0;
|
||||||
memcpy(buffer_copy.data(), props->dst_buffer_, buffer_size);
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
frame_snapshot = props->front_frame_;
|
||||||
|
video_width = props->video_width_;
|
||||||
|
video_height = props->video_height_;
|
||||||
|
}
|
||||||
|
|
||||||
int video_width = props->video_width_;
|
if (frame_snapshot && !frame_snapshot->empty() && video_width > 0 &&
|
||||||
int video_height = props->video_height_;
|
video_height > 0) {
|
||||||
|
std::vector<unsigned char> buffer_copy(*frame_snapshot);
|
||||||
std::string remote_id = props->remote_id_;
|
std::string remote_id = props->remote_id_;
|
||||||
std::string remote_host_name = props->remote_host_name_;
|
std::string remote_host_name = props->remote_host_name_;
|
||||||
std::string password =
|
std::string password =
|
||||||
@@ -1824,9 +1830,15 @@ void Render::CleanSubStreamWindowProperties(
|
|||||||
props->stream_texture_ = nullptr;
|
props->stream_texture_ = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props->dst_buffer_) {
|
{
|
||||||
delete[] props->dst_buffer_;
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
props->dst_buffer_ = nullptr;
|
props->front_frame_.reset();
|
||||||
|
props->back_frame_.reset();
|
||||||
|
props->video_width_ = 0;
|
||||||
|
props->video_height_ = 0;
|
||||||
|
props->video_size_ = 0;
|
||||||
|
props->render_rect_dirty_ = true;
|
||||||
|
props->stream_cleanup_pending_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2112,10 +2124,22 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
|
|||||||
{
|
{
|
||||||
// std::shared_lock lock(client_properties_mutex_);
|
// std::shared_lock lock(client_properties_mutex_);
|
||||||
for (auto& [host_name, props] : client_properties_) {
|
for (auto& [host_name, props] : client_properties_) {
|
||||||
thumbnail_->SaveToThumbnail(
|
std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
|
||||||
(char*)props->dst_buffer_, props->video_width_,
|
int video_width = 0;
|
||||||
props->video_height_, host_name, props->remote_host_name_,
|
int video_height = 0;
|
||||||
props->remember_password_ ? props->remote_password_ : "");
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
frame_snapshot = props->front_frame_;
|
||||||
|
video_width = props->video_width_;
|
||||||
|
video_height = props->video_height_;
|
||||||
|
}
|
||||||
|
if (frame_snapshot && !frame_snapshot->empty() && video_width > 0 &&
|
||||||
|
video_height > 0) {
|
||||||
|
thumbnail_->SaveToThumbnail(
|
||||||
|
(char*)frame_snapshot->data(), video_width, video_height,
|
||||||
|
host_name, props->remote_host_name_,
|
||||||
|
props->remember_password_ ? props->remote_password_ : "");
|
||||||
|
}
|
||||||
|
|
||||||
if (props->peer_) {
|
if (props->peer_) {
|
||||||
std::string client_id = (host_name == client_id_)
|
std::string client_id = (host_name == client_id_)
|
||||||
@@ -2209,18 +2233,52 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
|
|||||||
if (!props) {
|
if (!props) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (props->video_width_ <= 0 || props->video_height_ <= 0) {
|
std::shared_ptr<std::vector<unsigned char>> frame_snapshot;
|
||||||
|
int video_width = 0;
|
||||||
|
int video_height = 0;
|
||||||
|
bool render_rect_dirty = false;
|
||||||
|
bool cleanup_pending = false;
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
cleanup_pending = props->stream_cleanup_pending_;
|
||||||
|
if (!cleanup_pending) {
|
||||||
|
frame_snapshot = props->front_frame_;
|
||||||
|
video_width = props->video_width_;
|
||||||
|
video_height = props->video_height_;
|
||||||
|
}
|
||||||
|
render_rect_dirty = props->render_rect_dirty_;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cleanup_pending) {
|
||||||
|
if (props->stream_texture_) {
|
||||||
|
SDL_DestroyTexture(props->stream_texture_);
|
||||||
|
props->stream_texture_ = nullptr;
|
||||||
|
}
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
props->stream_cleanup_pending_ = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (render_rect_dirty) {
|
||||||
|
UpdateRenderRect();
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
props->render_rect_dirty_ = false;
|
||||||
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (!props->dst_buffer_) {
|
|
||||||
|
if (video_width <= 0 || video_height <= 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (!frame_snapshot || frame_snapshot->empty()) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (props->stream_texture_) {
|
if (props->stream_texture_) {
|
||||||
if (props->video_width_ != props->texture_width_ ||
|
if (video_width != props->texture_width_ ||
|
||||||
props->video_height_ != props->texture_height_) {
|
video_height != props->texture_height_) {
|
||||||
props->texture_width_ = props->video_width_;
|
props->texture_width_ = video_width;
|
||||||
props->texture_height_ = props->video_height_;
|
props->texture_height_ = video_height;
|
||||||
|
|
||||||
SDL_DestroyTexture(props->stream_texture_);
|
SDL_DestroyTexture(props->stream_texture_);
|
||||||
// props->stream_texture_ = SDL_CreateTexture(
|
// props->stream_texture_ = SDL_CreateTexture(
|
||||||
@@ -2245,8 +2303,8 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
|
|||||||
SDL_DestroyProperties(nvProps);
|
SDL_DestroyProperties(nvProps);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
props->texture_width_ = props->video_width_;
|
props->texture_width_ = video_width;
|
||||||
props->texture_height_ = props->video_height_;
|
props->texture_height_ = video_height;
|
||||||
// props->stream_texture_ = SDL_CreateTexture(
|
// props->stream_texture_ = SDL_CreateTexture(
|
||||||
// stream_renderer_, stream_pixformat_,
|
// stream_renderer_, stream_pixformat_,
|
||||||
// SDL_TEXTUREACCESS_STREAMING, props->texture_width_,
|
// SDL_TEXTUREACCESS_STREAMING, props->texture_width_,
|
||||||
@@ -2267,8 +2325,14 @@ void Render::ProcessSdlEvent(const SDL_Event& event) {
|
|||||||
SDL_DestroyProperties(nvProps);
|
SDL_DestroyProperties(nvProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_,
|
SDL_UpdateTexture(props->stream_texture_, NULL, frame_snapshot->data(),
|
||||||
props->texture_width_);
|
props->texture_width_);
|
||||||
|
|
||||||
|
if (render_rect_dirty) {
|
||||||
|
UpdateRenderRect();
|
||||||
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
|
props->render_rect_dirty_ = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -123,8 +123,13 @@ class Render {
|
|||||||
float mouse_diff_control_bar_pos_y_ = 0;
|
float mouse_diff_control_bar_pos_y_ = 0;
|
||||||
double control_bar_button_pressed_time_ = 0;
|
double control_bar_button_pressed_time_ = 0;
|
||||||
double net_traffic_stats_button_pressed_time_ = 0;
|
double net_traffic_stats_button_pressed_time_ = 0;
|
||||||
unsigned char* dst_buffer_ = nullptr;
|
// Double-buffered NV12 frame storage. Written by decode callback thread,
|
||||||
size_t dst_buffer_capacity_ = 0;
|
// consumed by SDL main thread.
|
||||||
|
std::mutex video_frame_mutex_;
|
||||||
|
std::shared_ptr<std::vector<unsigned char>> front_frame_;
|
||||||
|
std::shared_ptr<std::vector<unsigned char>> back_frame_;
|
||||||
|
bool render_rect_dirty_ = false;
|
||||||
|
bool stream_cleanup_pending_ = false;
|
||||||
float mouse_pos_x_ = 0;
|
float mouse_pos_x_ = 0;
|
||||||
float mouse_pos_y_ = 0;
|
float mouse_pos_y_ = 0;
|
||||||
float mouse_pos_x_last_ = 0;
|
float mouse_pos_x_last_ = 0;
|
||||||
@@ -607,8 +612,8 @@ class Render {
|
|||||||
uint64_t last_frame_time_;
|
uint64_t last_frame_time_;
|
||||||
bool show_new_version_icon_ = false;
|
bool show_new_version_icon_ = false;
|
||||||
bool show_new_version_icon_in_menu_ = true;
|
bool show_new_version_icon_in_menu_ = true;
|
||||||
uint64_t new_version_icon_last_trigger_time_ = 0;
|
double new_version_icon_last_trigger_time_ = 0.0;
|
||||||
uint64_t new_version_icon_render_start_time_ = 0;
|
double new_version_icon_render_start_time_ = 0.0;
|
||||||
#ifdef __APPLE__
|
#ifdef __APPLE__
|
||||||
bool show_request_permission_window_ = true;
|
bool show_request_permission_window_ = true;
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -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"
|
||||||
@@ -237,31 +238,31 @@ void Render::OnReceiveVideoBufferCb(const XVideoFrame* video_frame,
|
|||||||
render->client_properties_.find(remote_id)->second.get();
|
render->client_properties_.find(remote_id)->second.get();
|
||||||
|
|
||||||
if (props->connection_established_) {
|
if (props->connection_established_) {
|
||||||
if (!props->dst_buffer_) {
|
{
|
||||||
props->dst_buffer_capacity_ = video_frame->size;
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
props->dst_buffer_ = new unsigned char[video_frame->size];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (props->dst_buffer_capacity_ < video_frame->size) {
|
if (!props->back_frame_) {
|
||||||
delete props->dst_buffer_;
|
props->back_frame_ =
|
||||||
props->dst_buffer_capacity_ = video_frame->size;
|
std::make_shared<std::vector<unsigned char>>(video_frame->size);
|
||||||
props->dst_buffer_ = new unsigned char[video_frame->size];
|
}
|
||||||
}
|
if (props->back_frame_->size() != video_frame->size) {
|
||||||
|
props->back_frame_->resize(video_frame->size);
|
||||||
|
}
|
||||||
|
|
||||||
memcpy(props->dst_buffer_, video_frame->data, video_frame->size);
|
std::memcpy(props->back_frame_->data(), video_frame->data,
|
||||||
bool need_to_update_render_rect = false;
|
video_frame->size);
|
||||||
if (props->video_width_ != props->video_width_last_ ||
|
|
||||||
props->video_height_ != props->video_height_last_) {
|
|
||||||
need_to_update_render_rect = true;
|
|
||||||
props->video_width_last_ = props->video_width_;
|
|
||||||
props->video_height_last_ = props->video_height_;
|
|
||||||
}
|
|
||||||
props->video_width_ = video_frame->width;
|
|
||||||
props->video_height_ = video_frame->height;
|
|
||||||
props->video_size_ = video_frame->size;
|
|
||||||
|
|
||||||
if (need_to_update_render_rect) {
|
const bool size_changed = (props->video_width_ != video_frame->width) ||
|
||||||
render->UpdateRenderRect();
|
(props->video_height_ != video_frame->height);
|
||||||
|
if (size_changed) {
|
||||||
|
props->render_rect_dirty_ = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
props->video_width_ = video_frame->width;
|
||||||
|
props->video_height_ = video_frame->height;
|
||||||
|
props->video_size_ = video_frame->size;
|
||||||
|
|
||||||
|
props->front_frame_.swap(props->back_frame_);
|
||||||
}
|
}
|
||||||
|
|
||||||
SDL_Event event;
|
SDL_Event event;
|
||||||
@@ -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;
|
||||||
@@ -574,6 +613,10 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
|
|||||||
render->signal_connected_ = false;
|
render->signal_connected_ = false;
|
||||||
} else if (SignalStatus::SignalServerClosed == status) {
|
} else if (SignalStatus::SignalServerClosed == status) {
|
||||||
render->signal_connected_ = false;
|
render->signal_connected_ = false;
|
||||||
|
} else if (SignalStatus::SignalFingerprintMismatch == status) {
|
||||||
|
render->signal_connected_ = false;
|
||||||
|
LOG_ERROR("[{}] signal server fingerprint mismatch", client_id);
|
||||||
|
render->config_center_->ClearDefaultCertFingerprint();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (client_id.rfind("C-", 0) != 0) {
|
if (client_id.rfind("C-", 0) != 0) {
|
||||||
@@ -601,6 +644,9 @@ void Render::OnSignalStatusCb(SignalStatus status, const char* user_id,
|
|||||||
props->signal_connected_ = false;
|
props->signal_connected_ = false;
|
||||||
} else if (SignalStatus::SignalServerClosed == status) {
|
} else if (SignalStatus::SignalServerClosed == status) {
|
||||||
props->signal_connected_ = false;
|
props->signal_connected_ = false;
|
||||||
|
} else if (SignalStatus::SignalFingerprintMismatch == status) {
|
||||||
|
props->signal_connected_ = false;
|
||||||
|
LOG_ERROR("[{}] signal server fingerprint mismatch", remote_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -681,12 +727,22 @@ void Render::OnConnectionStatusCb(ConnectionStatus status, const char* user_id,
|
|||||||
case ConnectionStatus::Closed: {
|
case ConnectionStatus::Closed: {
|
||||||
props->connection_established_ = false;
|
props->connection_established_ = false;
|
||||||
props->mouse_control_button_pressed_ = false;
|
props->mouse_control_button_pressed_ = false;
|
||||||
if (props->dst_buffer_ && props->stream_texture_) {
|
|
||||||
memset(props->dst_buffer_, 0, props->dst_buffer_capacity_);
|
{
|
||||||
SDL_UpdateTexture(props->stream_texture_, NULL, props->dst_buffer_,
|
std::lock_guard<std::mutex> lock(props->video_frame_mutex_);
|
||||||
props->texture_width_);
|
props->front_frame_.reset();
|
||||||
|
props->back_frame_.reset();
|
||||||
|
props->video_width_ = 0;
|
||||||
|
props->video_height_ = 0;
|
||||||
|
props->video_size_ = 0;
|
||||||
|
props->render_rect_dirty_ = true;
|
||||||
|
props->stream_cleanup_pending_ = true;
|
||||||
}
|
}
|
||||||
render->CleanSubStreamWindowProperties(props);
|
|
||||||
|
SDL_Event event;
|
||||||
|
event.type = render->STREAM_REFRESH_EVENT;
|
||||||
|
event.user.data1 = props.get();
|
||||||
|
SDL_PushEvent(&event);
|
||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,8 @@
|
|||||||
#include "rd_log.h"
|
#include "rd_log.h"
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
#define NEW_VERSION_ICON_RENDER_TIME_INTERVAL 2000
|
constexpr double kNewVersionIconBlinkIntervalSec = 2.0;
|
||||||
|
constexpr double kNewVersionIconBlinkOnTimeSec = 1.0;
|
||||||
|
|
||||||
namespace crossdesk {
|
namespace crossdesk {
|
||||||
|
|
||||||
@@ -106,13 +107,11 @@ int Render::TitleBar(bool main_window) {
|
|||||||
|
|
||||||
std::string about_str = localization::about[localization_language_index_];
|
std::string about_str = localization::about[localization_language_index_];
|
||||||
if (update_available_) {
|
if (update_available_) {
|
||||||
auto now_time = std::chrono::duration_cast<std::chrono::milliseconds>(
|
const double now_time = ImGui::GetTime();
|
||||||
std::chrono::steady_clock::now().time_since_epoch())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
// every 2 seconds
|
// every 2 seconds
|
||||||
if (now_time - new_version_icon_last_trigger_time_ >=
|
if (now_time - new_version_icon_last_trigger_time_ >=
|
||||||
NEW_VERSION_ICON_RENDER_TIME_INTERVAL) {
|
kNewVersionIconBlinkIntervalSec) {
|
||||||
show_new_version_icon_ = true;
|
show_new_version_icon_ = true;
|
||||||
new_version_icon_render_start_time_ = now_time;
|
new_version_icon_render_start_time_ = now_time;
|
||||||
new_version_icon_last_trigger_time_ = now_time;
|
new_version_icon_last_trigger_time_ = now_time;
|
||||||
@@ -120,9 +119,9 @@ int Render::TitleBar(bool main_window) {
|
|||||||
|
|
||||||
// render for 1 second
|
// render for 1 second
|
||||||
if (show_new_version_icon_) {
|
if (show_new_version_icon_) {
|
||||||
about_str = about_str + " " + ICON_FA_TRIANGLE_EXCLAMATION;
|
about_str = about_str + " " + ICON_FA_CIRCLE_ARROW_UP;
|
||||||
if (now_time - new_version_icon_render_start_time_ >=
|
if (now_time - new_version_icon_render_start_time_ >=
|
||||||
NEW_VERSION_ICON_RENDER_TIME_INTERVAL / 2) {
|
kNewVersionIconBlinkOnTimeSec) {
|
||||||
show_new_version_icon_ = false;
|
show_new_version_icon_ = false;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -151,13 +150,11 @@ int Render::TitleBar(bool main_window) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (update_available_ && show_new_version_icon_in_menu_) {
|
if (update_available_ && show_new_version_icon_in_menu_) {
|
||||||
auto now_time = std::chrono::duration_cast<std::chrono::milliseconds>(
|
const double now_time = ImGui::GetTime();
|
||||||
std::chrono::steady_clock::now().time_since_epoch())
|
|
||||||
.count();
|
|
||||||
|
|
||||||
// every 2 seconds
|
// every 2 seconds
|
||||||
if (now_time - new_version_icon_last_trigger_time_ >=
|
if (now_time - new_version_icon_last_trigger_time_ >=
|
||||||
NEW_VERSION_ICON_RENDER_TIME_INTERVAL) {
|
kNewVersionIconBlinkIntervalSec) {
|
||||||
show_new_version_icon_ = true;
|
show_new_version_icon_ = true;
|
||||||
new_version_icon_render_start_time_ = now_time;
|
new_version_icon_render_start_time_ = now_time;
|
||||||
new_version_icon_last_trigger_time_ = now_time;
|
new_version_icon_last_trigger_time_ = now_time;
|
||||||
@@ -166,14 +163,13 @@ int Render::TitleBar(bool main_window) {
|
|||||||
// render for 1 second
|
// render for 1 second
|
||||||
if (show_new_version_icon_) {
|
if (show_new_version_icon_) {
|
||||||
ImGui::SetWindowFontScale(0.6f);
|
ImGui::SetWindowFontScale(0.6f);
|
||||||
ImGui::SetCursorPos(
|
ImGui::SetCursorPos(ImVec2(bar_pos_x + title_bar_button_width * 0.21f,
|
||||||
ImVec2(bar_pos_x + title_bar_button_width * 0.15f,
|
bar_pos_y - title_bar_button_width * 0.24f));
|
||||||
bar_pos_y - title_bar_button_width * 0.325f));
|
ImGui::Text(ICON_FA_CIRCLE_ARROW_UP);
|
||||||
ImGui::Text(ICON_FA_TRIANGLE_EXCLAMATION);
|
|
||||||
ImGui::SetWindowFontScale(1.0f);
|
ImGui::SetWindowFontScale(1.0f);
|
||||||
|
|
||||||
if (now_time - new_version_icon_render_start_time_ >=
|
if (now_time - new_version_icon_render_start_time_ >=
|
||||||
NEW_VERSION_ICON_RENDER_TIME_INTERVAL / 2) {
|
kNewVersionIconBlinkOnTimeSec) {
|
||||||
show_new_version_icon_ = false;
|
show_new_version_icon_ = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
Submodule submodules/minirtc updated: e2623281d6...27f721015b
Reference in New Issue
Block a user